{High level}, I am trying to post back an ASP.NET MVC view which has multiple 'user inputs' of "search criteria" and use this varying "search criteria" as parameters in the Controller action method which constructs a viewmodel and gets the results from a method of the viewmodel by ultimately passing the 'user inputs' as parameters to that method's {Low level} .FindAll(). Need the ability for user to use one or many or all inputs as their search criteria. For example: User wants to search for Titles which have "John" as [CustFName]. Another example: User wants to search for Titles which have both "John" as [CustFName] and "Holmes" as [CustLName].
public class SearchTitleViewModel
{
private readonly ApplicationDbContext _db = new ApplicationDbContext();
public SearchTitleViewModel()
{
var tits = _db.Titles.ToList();
}
public Title Title { get; set; }
public List<Title> Titles { get; set; }
public SearchTitleViewModel GetTitlesWithSearchCriteria(string office, bool? mv = null, int id = -1, string custFName = null, string custLName = null, string vin = null)
{
SearchTitleViewModel searchtitlevm = new SearchTitleViewModel();
if (id > -1)
{
searchtitlevm.Title = _db.Titles.Find(id);
searchtitlevm.Titles.Add(searchtitlevm.Title);
return searchtitlevm;
}
searchtitlevm.Titles.AddRange(
Titles.FindAll(
tit => tit.CustFName == custFName && // want to make "tit.CustFName == custFName" a variable
tit.CustLName == custLName &&// want to make "tit.CustFName == custFName" a variable
tit.InitialLocation == office
)
);
return searchtitlevm;
}
}
Ultimately I would like to pass in multiple lambda expressions to .FindAll() as variables that are declared at runtime like FindAll(Title tit, param1 custFNameExpression, param2 custLNameExpression, ...) but have not been able to find any examples to do something like this. I did find examples of creating a delegate[] but still does not solve the issue of adding an expression or not based on if the user supplied that one or not from the view.
Am I going about this all wrong? I mean is there a simpler way to do this?
I gave up trying to make .FindAll() work for what I needed. I used .SqlQuery() instead:
public class SearchTitleViewModel
{
private readonly ApplicationDbContext _db = new ApplicationDbContext();
public SearchTitleViewModel()
{
Title = new Title()
{
NtyId = 1//for testing REMOVE
};
Titles = new List<Title>();
}
public Title Title { get; set; }
public List<Title> Titles { get; set; }
public SearchTitleViewModel GetTitlesWithSearchCriteria(SearchTitleViewModel vm)
{
var searchtitlevm = new SearchTitleViewModel();
if (vm.Title.TitleNum != null)
{
searchtitlevm.Title = _db.Titles.Find(vm.Title.TitleNum);
if (searchtitlevm.Title != null && searchtitlevm.Title.NtyId != vm.Title.NtyId)
{
return searchtitlevm;
}
searchtitlevm.Titles.Add(searchtitlevm.Title);
return searchtitlevm;
}
var titssql = "SELECT * FROM Titles WHERE";
if (vm.Title.Vin != null) { titssql += $" Vin = \'{vm.Title.Vin}\' AND "; }
if (vm.Title.CustLName != null) { titssql += $" CustLName = \'{vm.Title.CustLName}\' AND "; }
if (vm.Title.CustFName != null) { titssql += $" CustFName = \'{vm.Title.CustFName}\' AND "; }
titssql += $" NtyId = \'{vm.Title.NtytyId}\'";
searchtitlevm.Titles = _db.Titles.SqlQuery(titssql).ToList();
return searchtitlevm;
}
}
Related
I have Form with ComboBox and TextBox. The first contains the column names, and the second contains the text to search for. As a source, ComboBox takes ListTypeSearch from ItemSearch elements. The Search() method is called in the processing of pressing the Search button.
If give the column a name like this, nothing will be found
EF.Functions.Like(item.Value, ...); // Value = "FullName"
If specify a column from the model, the search works
EF.Functions.Like(w.FullName, ...);
Is it possible to replace the column that should be searched within the same Search() method?
ListTypeSearch.Add(new ItemSearch { Value = "FullName", Display = "some text" });
ListTypeSearch.Add(new ItemSearch { Value = "PassportSeries", Display = "some text" });
ListTypeSearch.Add(new ItemSearch { Value = "PassportNumber", Display = "some text" });
public class ItemSearch
{
public string Value { get; set; }
public string Display { get; set; }
}
internal List<WorkerTableRow> Search(ItemSearch item, string text)
{
try
{
Found = new List<WorkerTableRow>();
using (ModelContext model = new ModelContext())
{
Found = (from w in model.Workers
where EF.Functions.Like(w.FullName, // this code
String.Format("%{0}%", text))
select new WorkerTableRow
{
...
})
.ToList();
}
}
catch (Exception ex) { ... }
return Found;
}
Update
Now I did like this. It's works. Can this be simplified?
where EF.Functions.Like(w.GetProperty(item.Value),
String.Format("%{0}%", text))
public partial class Workers
{
...
public string FullName { get; set; }
public string PassportSeries { get; set; }
public string PassportNumber { get; set; }
public string GetProperty(string name)
{
switch (name)
{
case "FullName":
return FullName;
case "PassportSeries":
return PassportSeries;
case "PassportNumber":
return PassportNumber;
default:
return string.Empty;
}
}
}
According other answer. If uses Like(w.GetProperty(item.Value), ...), the request is executed on the client, not on the server. To send the entire request to the server, you can do the following:
List<WorkerTableRow> Search(ItemSearch item, string text)
{
string pattern = string.Format("%{0}%", text);
using (var model = new ModelContext())
{
IQueryable<Worker> query = model.Workers;
if (item.Value == "FullName")
query = query.Where(w => EF.Functions.Like(w.FullName, pattern));
if (item.Value == "PassportSeries")
query = query.Where(w => EF.Functions.Like(w.PassportSeries, pattern));
if (item.Value == "PassportNumber")
query = query.Where(w => EF.Functions.Like(w.PassportNumber, pattern));
return query.Select(w => new WorkerTableRow { ... }).ToList();
}
}
In an MVC app, I want to implement a set of rules, which super users can create, read, update and delete.
Each rule explicitly allows/forbids a user to perform an action in the format:
<Allow || Deny> userId <action_key> <condition>
The action key would be something like "DoSomeAction" as a string.
I then intend to use those rules for authorisation inside controllers for
authorisation. For example:
//GET ViewProduct/id
public ActionResult ViewProduct(string productId)
{
var product = // get product from repository;
if(RulesAuthorizer.Authorise("ViewProduct", User.Identity.GetUserId(), product){
//proceed to product... JSON or partial view, etc.
}
return new HttpStatusCodeResult(403);
}
ViewProduct is an example action_key above.
In the Authorise(string action_key, string userId, object ruleArg = null) method, I would load all the user relevant rules from the DB for this action key and decide if the user should be allowed.
However, and this is really the question, how could I use a condition for a rule as a string. For example, a condition would be:
a user must be a member of the group "Customers" and the product must not be of type "cheese" or
a custom method result such as if the product was added by group X, and group Y must not see it, I could have my method Product.GetAddedBy() and include this method in the LINQ expression.
How could I store such conditions as strings for each rule and then build LINQ expressions from them?
I intend to pass the object in question (Product in the example) in the optional ruleArg parameter.
Any ideas are much appreciated for a convenient way to store strings, which can be made into LINQ expressions at run time or any alternative approach such as perhaps map conditions to delegates with parameters?
Here is an example of user access via Attributes using strings to determine what they have access to. This is using Action/Controller to determine access but you can modify it for any string(s) you want.
Decorate the controller(s) with [AuthoriseByRole]
First the Attribute
namespace PubManager.Authorisation
{
public class AuthoriseByRoleAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized && httpContext.Request.IsAjaxRequest())
{
httpContext.Response.StatusCode = 401;
httpContext.Response.End();
}
if (isAuthorized)
{
var request = httpContext.Request;
var r = request.RequestContext.RouteData.Values["r"]
?? request["r"];
var currentUser = (UserModel) HttpContext.Current.Session["user"];
if (currentUser == null)
{
currentUser = HttpContext.Current.User.GetWebUser();
}
var rd = httpContext.Request.RequestContext.RouteData;
string currentAction = rd.GetRequiredString("action");
string currentController = rd.GetRequiredString("controller");
if (currentUser.HasAccess(currentController, currentAction))
return true;
}
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return false;
}
}
}
Then the UserModel that is used to determine access:
namespace PubManager.Domain.Users
{
public class UserModel
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Title { get; set; }
[Required]
[DisplayName("Forename")]
public string FirstName { get; set; }
[Required]
public string Surname { get; set; }
[Required]
[DisplayName("Company name")]
public DateTime? LastLogin { get; set; }
public bool LockedOut { get; set; }
public DateTime? LockedOutUntil { get; set; }
public bool IsGlobalAdmin { get; set; }
public bool? IsSystemUser { get; set; }
public IEnumerable<RoleModel> Roles { get; set; }
public bool HasAccess(string controller, string view)
{
if (IsGlobalAdmin || IsSystemUser.Value)
{
return true;
}
var isAuthorized = false;
if (!Roles.Any())
return false;
foreach (var role in Roles)
{
if (role.PageToRoles == null)
return false;
foreach (var pg in role.PageToRoles)
{
if (pg.RolePage.Controller.Equals(controller, StringComparison.InvariantCultureIgnoreCase) && (pg.RolePage.View.Equals(view, StringComparison.InvariantCultureIgnoreCase) || pg.RolePage.View.Equals("*")))
isAuthorized = true;
}
}
return isAuthorized;
}
}
}
Finally the GetWebUser class to get the user
namespace PubManager.Domain.Users
{
public static class SecurityExtensions
{
public static string Name(this IPrincipal user)
{
return user.Identity.Name;
}
public static UserModel GetWebUser(this IPrincipal principal)
{
if (principal == null)
return new UserModel();
var db = new DataContext();
var user = (from usr in db.Users
where usr.UserName == principal.Identity.Name
select new UserModel
{
Title = usr.Person.Title,
UserName = usr.UserName,
FirstName = usr.Person.FirstName,
Surname = usr.Person.LastName,
Email = usr.Person.Email,
LockedOut = usr.LockedOut,
UserId = usr.UserId,
IsSystemUser = usr.IsSystemUser,
IsGlobalAdmin = usr.IsGlobalAdmin.Value,
PersonId = usr.PersonId,
Roles = from r in usr.UserToRoles
select new RoleModel
{
RoleId = r.RoleId,
PageToRoles = from ptr in r.Role.PageToRoles
select new PageToRoleModel
{
RolePage = new RolePageModel
{
Controller = ptr.RolePage.Controller,
View = ptr.RolePage.View
}
}
}
}).FirstOrDefault();
if (user != null)
{
HttpContext.Current.Session["user"] = user;
}
return user;
}
}
}
I am trying to build a basic dependency injection container to give myself a better understand of how they work. One of the parts of my container is mapping an alias to a class name. This class can then be mapped recursively to produce a hierarchical list. It must also handle cases where it is mapped to itself.
For example say I have the following mapping:
var map = new List<Map>() {
{ new Map("Foo", "Foo") },
{ new Map("foo", "Foo") }
};
Where Map is:
public class Map {
public string Name { get; set; }
public string Class { get; set; }
public Map(string name, string #class) {
Name = name;
Class = #class;
}
}
I'd like to write a method (GetMap) which will return the root (name) and the highest level parent (class) for a given name. For example say I said:
var map = GetMap(map, "Foo");
var map2 = GetMap(map, "foo");
Both variables would return a new Map with the name "foo" and class "Foo". Here's another example to further explain. Given the following map:
var map = new List<Map>() {
{ new Map("Foo", "FooExtended") },
{ new Map("foo", "Foo") }
};
It would produce a new Map with the Name "foo" and class "FooExtended".
Here is my first attempt:
private Map GetMap(List<Map> map, string #class, string name = null) {
var item = map.SingleOrDefault(m => m.Name == #class);
if (item != null && #class != item.Class) {
return GetMap(map, item.Class, #class);
} else {
return new Map(name ?? #class, #class);
}
}
This correctly returns a Map with the correct class for both examples. However the name returned is "Foo" for both examples. This should be "foo" since that is the root of the hierarchy.
I hope I've explained that well enough. I'd appreciate it if someone could show me what I am doing wrong. Thanks
Try this
public Map GetMap(List<Map> map, string name, string finalName = null) {
var item = map.SingleOrDefault(m => m.Name == name);
if (item != null && name != item.Class) {
return GetMap(map, item.Class, finalName ?? name); // <- for you, name ?? #class
} else {
return new Map(finalName ?? name, name);
}
}
You need to pass the name (or in my case finalName) along the entire execution path
EDIT - This isn't a full solution but I am leaving it
I renamed the parameters in my test so it was easier for me to read, sorry about that.
My entire LinqPad code
void Main()
{
var map = new List<Map>() {
{ new Map("FooExtended", "FooExtended2") },
{ new Map("Foo", "FooExtended") },
{ new Map("foo", "Foo") }
};
var newMap = GetMap(map, "foo");
newMap.Dump(); // Gives Map("foo", "FooExtended2")
}
public class Map {
public string Name { get; set; }
public string Class { get; set; }
public Map(string name, string #class) {
Name = name;
Class = #class;
}
}
private static Map GetMap(List<Map> map, string name, string finalName = null) {
var item = map.SingleOrDefault(m => m.Name == name);
if (item != null && name != item.Class) {
return GetMap(map, item.Class, finalName ?? name);
} else {
return new Map(finalName ?? name, name);
}
}
EDIT AGAIN - Actual answer based on comments
Final answer, GetMap will return the name of the highest type (EG, foo) and the class of the lowest (EG Foo)
private static Map GetMap(List<Map> map, string name) {
return new Map(GetMapName(map, name), GetMapClass(map, name));
}
private static string GetMapName(List<Map> map, string name) {
var item = map.SingleOrDefault(m => m.Name != name && m.Class == name);
return item != null ? GetMapName(map, item.Name) : name;
}
private static string GetMapClass(List<Map> map, string name) {
var item = map.SingleOrDefault(m => m.Name == name && m.Class != name);
return item != null ? GetMapClass(map, item.Class) : name;
}
I used the code I posted above and I get "foo", "FooExtended2" no matter what name or class I use.
Hope that helps.
I'm trying to implement filtering and paging in a MVC controller. I pass information to View using ViewBag.
Filter class is this:
public class Criteria
{
public int? SnapshotKey { get; set; }
public string Delq { get; set; }
public override string ToString()
{
string[] Ares = new string[2];
if (SnapshotKey.HasValue)
Ares[0] = "SnapshotKey=" + SnapshotKey.ToString();
if (!String.IsNullOrWhiteSpace(Delq))
Ares[1] = "Delq=" + Delq;
return String.Join("&", Ares.Where(s => !string.IsNullOrWhiteSpace(s)));
}
}
My controller method is like this:
public ActionResult Search(Criteria filter, string filterString,
int pageNo = 1, int pageSize = 5)
{
using (DemoDBEntities db = new DemoDBEntities())
{
var list = db.BMH.AsQueryable();
if (filter != null)
{
if (filter.SnapshotKey.HasValue)
list = list.Where(r => r.SnapshotKey == filter.SnapshotKey.Value);
if (!String.IsNullOrWhiteSpace(filter.Delq))
list = list.Where(r => r.Delq == filter.Delq);
}
list = list.OrderBy(r=>r.SnapshotKey).Skip((pageNo - 1) * pageSize).Take(pageSize);
ViewBag.Criteria = filter.ToString();
ViewBag.CriteriaString = filterString;
ViewBag.PageNo = pageNo;
return View(list.ToList());
}
}
AFAIK, I cannot pass ViewBag as an object to controller, that's why I used filter.ToString() to store the current filter.
In View, I have below link to go to specific page, while preserving current filter.
#Html.ActionLink("Next Page", "Search", new { filter = ViewBag, filterString = ViewBag.CriteriaString, pageNo = ViewBag.PageNo + 1, pageSize = 5 })
So when coming back from View, I get current filter as string. Now in controller I need to convert string to Criteria class. It is doable but I'm looking for a more decent way to do what I need.
The value of filterString in the Search() method will be a string in the format name=value&name=value... so you could first using String.Split() (on the & character) to create an array of name=value items, and then split again (on the = character) to get the property names and values, but that is all getting messy and it would be easier to just build the whole query string and have it bind directly to your Criteria model.
Change the model to include all properties, including pageNo and pageSize`
public class Criteria
{
public Criteria() // perhaps add some defaults?
{
PageNo = 1;
PageSize = 5;
}
public int? SnapshotKey { get; set; }
public string Delq { get; set; }
public int PageNo { get; set; }
public int PageSize { get; set; }
.... // see method below
}
Then to make it flexible and allow you to add more 'criteria' properties, use reflection to build the query string
public string ToQueryString(int pageIncrement)
{
List<string> propValues = new List<string>();
foreach(var prop in GetType().GetProperties())
{
var name = prop.Name;
var value = prop.GetValue(this);
if (name == "PageNo")
{
value == (int)value + pageIncrement;
}
if (value != null)
{
propValues .Add(String.Format("{0}={1}", name, value));
}
}
return "?" + String.Join("&", propValues);
}
The code in the controller will then be
public ActionResult Search(Criteria filter)
{
using (DemoDBEntities db = new DemoDBEntities())
{
var list = db.BMH.AsQueryable();
if (filter != null)
{
if (filter.SnapshotKey.HasValue)
list = list.Where(r => r.SnapshotKey == filter.SnapshotKey.Value);
if (!String.IsNullOrWhiteSpace(filter.Delq))
list = list.Where(r => r.Delq == filter.Delq);
}
list = list.OrderBy(r => r.SnapshotKey).Skip((filter.PageNo - 1) * pageSize).Take(filter.PageSize);
ViewBag.Criteria = filter;
return View(list.ToList());
}
}
and then in the view
Previous
Next
Note also that you could just use
#Html.ActionLink("Next", "Search", (yourAssembly.Criteria)ViewBag.Criteria)
assuming that Criteria contains only simple properties, meaning the ToQueryString() method is not required. However you would need to increment/decrement the value of the PageNo property before using ActionLink(), for example
#{ var criteria = (yourAssembly.Criteria)ViewBag.Criteria; }
#{ criteria.PageNo = #criteria.PageNo - 1; }
#Html.ActionLink("Previous", "Search", criteria)
#{ criteria.PageNo = #criteria.PageNo + 2; }
#Html.ActionLink("Next", "Search", criteria)
I have a search method that is trying to match a director name to an entry in one and/or two arrays. However, I cannot figure out how to print the names of multiple titles if the director has more than one movie in either array.
Right now my code looks like this:
if (director == myDVDs[i].Director || director == myBlu[i].Director)
{
Console.WriteLine("{0}: {1}", i + 1, myBlu[i].Title);
EndOptions();
}
else if (director != myDVDs[i].Director && director != myBlu[i].Director)
{
Console.WriteLine("{0} does not have a movie in the database try again", director);
Console.Clear();
Search();
}
You can use LINQ's Concat, like this:
var matching = myDVDs.Concat(myBlu).Where(d => d.Director == director);
int count = 1;
foreach (var m in matching) {
Console.WriteLine("{0}: {1}", count++, m.Title);
}
Generic solution which simplyfies adding a new movie type. To find titles used LINQ Select(), Where(), SelectMany(), yo might need adding using System.Linq to use these methods:
var dvds = new List<IMovie>
{
new DvdMovie {Title = "DVD1", Director = "Director1"},
new DvdMovie {Title = "DVD2", Director = "Director1"},
new DvdMovie {Title = "DVD3", Director = "Director2"}
};
var bluerays = new List<IMovie>
{
new BlueRayMovie {Title = "BR1", Director = "Director3"},
new BlueRayMovie {Title = "BR2", Director = "Director3"},
new BlueRayMovie {Title = "BR3", Director = "Director1"}
};
var allMovies = new List<IEnumerable<IMovie>> {dvds, bluerays};
string searchFor = "Director1";
// Main query, all other initialization code and types are below
IEnumerable<string> titles = allMovies.SelectMany(l =>
l.Where(sl => sl.Director == searchFor)
.Select(m => m.Title));
if (titles != null && titles.Count() > 0)
{
// OUTPUTs: DVD1, DVD2, BR3
foreach (var title in titles)
{
Console.WriteLine(title);
}
}
else
{
Console.WriteLine("Nothing found for " + searchFor);
}
Types
public interface IMovie
{
string Director { get; set; }
string Title { get; set; }
}
// TODO: Make immutable.
// Make setter private and introduce parametrized constructor
public class DvdMovie : IMovie
{
public string Director { get; set; }
public string Title { get; set; }
}
// TODO: Make immutable.
// Make setter private and introduce parametrized constructor
public class BlueRayMovie : IMovie
{
public string Director { get; set; }
public string Title { get; set; }
}