I have a view that displays a table of data that I want to be able to sort by specifying a property on the row type.
My model (so far) is:
class Model
{
List<DataType> data;
Expression<Func<DataType, object>> SortProperty;
}
I've tried creating that in the view to be passed into my controller as follows:
<a href='<%= Url.Action("Index", "Approvals", new IndexModel() { Page = Model.Page, SortProperty = ((ApprovalModel m) => m.Id)}) %>'>Id</a>
which renders as:
<a href='/PartsLegislation/Approvals.aspx/Approvals?SortProperty=m%20%3D%3E%20Convert(m.Id)'>Id</a>
so it sort of looks like it'll work (all-be-it with a Convert expression in there), however in the controller SortProperty is always null.
TL;DR: How can I pass an expression pointing to a property from my view to the controller?
Edit: My controller action is as below:
[HttpGet]
public ActionResult Index(Model viewModel)
{
....
viewModel.Approvals = PartsDC.Repository<Approval>()
.Where(a => !a.Deleted)
.OrderBy(viewModel.SortExpression)
.Skip((viewModel.Page ?? 0) * RowsPerPage)
.Take(RowsPerPage)
.Select(a => Mapper.Map<Approval, ApprovalHeaderModel>(a))
.ToList();
...
}
As far as I know you can't pass complex objects via URL. You can pass for example string with name of property.
However if you want to just sort table maybe you should think about another approach to this problem. Check this http://www.kryogenix.org/code/browser/sorttable/ .
It allows you to simply sort your table by clicking on headers.
Related
I have a model that has a PersonId int, and also, a List<SelectListItems> People for people. The UI allows me to select a person from the drop down model, and save the value into PersonId.
That works, but if there is an error in my ModelState, caused by another field, I do this:
if (ModelState.IsValid == false)
{
return View(model);
}
Problem is, the object holding the list of people is NULL in the returned model. Do I really need to repopulate it from the database again, or can this somehow be 'stored', and only populated when I initially create the view?
Yes. You need to load the SelectView again.
Only values in your inputs are posted (hidden or normal ones)... so anything to be posted must be in those inputs.
If you want to avoid going to the database... you should cache that list.
Here you can see an example for the caching logic: https://stackoverflow.com/a/349111/7720
Yes, you need to repopulate it. Only the selected value of a dropdown list is sent via a form. It's perfectly normal to do this. In general, if you have data available on the server, it always makes sense to cache it/query for it again, rather than trusting any input from a user.
To conveniently build SelectLists, rather than using SelectListItem, I use a method on a base controller:
[NonAction]
public SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
object selectedValue = null)
{
var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;
return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}
Note the use of NonActionAttribute, which is used to indicate that a public controller method is not an action method.
Then in the controller, it's easy to build any list again, without polluting the actions too much. For example:
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
if (ModelState.IsValid)
return RedirectToAction("Success");
// The model wasn't valid, so repopulate the dropdown
model.People = BuildSelectList(db.People, m => m.Id,
m => m.Name, model.PersonId);
return View(model);
}
You could do something similar for SelectListItem, rather than manually rebuilding your lists each time.
The goal
I want to display in my view the results of stored procedure.
The problem
Entity Framework automatically imported for me a method that executes a procedure, however I'm not getting the results I expect displaying on the screen.
The imported function is:
public virtual ObjectResult<getProductsListForHome_Result> getProductsListForHome(Nullable<int> inOffer, Nullable<int> categoryId)
{
var inOfferParameter = inOffer.HasValue ?
new ObjectParameter("inOffer", inOffer) :
new ObjectParameter("inOffer", typeof(int));
var categoryIdParameter = categoryId.HasValue ?
new ObjectParameter("categoryId", categoryId) :
new ObjectParameter("categoryId", typeof(int));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<getProductsListForHome_Result>("getProductsListForHome", inOfferParameter, categoryIdParameter);
}
What I have already tried
On ProductsController:
//
// GET: /Products/
public ActionResult Index()
{
ObjectResult<getProductsListForHome_Result> products = db.getProductsListForHome(1, 14);
return View(products.ToList());
}
Using the previous code, when I access http://myapp.com/Products/ I'm getting the following message:
The model item passed into the dictionary is of type
'System.Collections.Generic.List1[MyApp.Models.getProductsListForHome_Result]',
but this dictionary requires a model item of type
'System.Collections.Generic.IEnumerable1[MyApp.Models.bm_products]'.
What do I have to do to resolve this?
First, well-written question!
This is a type-casting problem, and it looks like your answer is the accepted answer here:
MVC: The model item passed into the dictionary is of type X, but this dictionary requires a model item of type X
Most likely your View is a strongly typed one, and it is declared as
#model System.Collections.Generic.IEnumerable<MyApp.Models.bm_products>
However you are passing it a different type in controller, and experiencing the error.
What you can do:
Specify another type for the View. View itself might require some refactoring after this:
#model System.Collections.Generic.IEnumerable<MyApp.Models.getProductsListForHome_Result>
Preferable. Run some code in controller to convert collection returned from SP into something View can consume:
public ActionResult Index()
{
ObjectResult<getProductsListForHome_Result> products = db.getProductsListForHome(1, 14);
List<bm_products> viewProducts = products.Select(p => new bm_products{ProductName = p.Name, ProductPrice = p.Price}).ToList();
return View(viewProducts);
}
I'm a beginner with ASP MVC and I'm trying to show data from a model in a view.
This is how I display the data :
#Html.DisplayFor(modelItem => item.Budget_Year)
But I don't know how to use this data, for example I tried to round up this result and I tried naively :
#{
double test = (modelItem => item.Budget_Year);
test = System.Math.Round(test , 2);
}
But I can't use it like that : Cannot convert lambda expression to type 'double' because it is not a delegate type
Someone can explain me how to use this different items from my model in my view ?
Best regards,
Alex
you have many ways to do this more properly :
use a ViewModel class, where you have a property which is your Rounded value
public class MyViewModel {
public double BudgetYear {get;set;}
public double RoundedBudgetYear {get {return Math.Round(BudgetYear, 2);}}
}
and in View
#Html.DisplayFor(m => m.RoundedBudgetYear)
or
Add a DisplayFormat attribute on your property
see Html.DisplayFor decimal format?
or
Create your own HtmlHelper, which will round the displayed value.
#Html.DisplayRoundedFor(m => m.BudgetYear)
First you need to declare what model you will actually be using and then use it as Model variable.
#model YourModelName
#{
var test = Model.BudgetYear.ToString("0.00");
}
If you're just trying to access a property of the model you can do it like this:
double test = Model.BudgetYear;
The lambda is only necessary if you're trying to have the user assign a value to it from the view.
I wouldn't do this in the view. Instead I would round BudgetYear in your model / view model and send it down to the View already rounded. Keep the logic in the controller / model and out of the view. This will make it easier to test as well
I have a controller action that uses PredicateBuilder to build a dynamic linq query. I want to pass the results of this query to a partial view. What is the best way to do this? If it's best practice to always use strongly typed views, should my view model that I pass to the controller action have a list that I can pass the results of the query into? Or is this just extra overhead using two lists?
Here's a simplified version of the controller action:
[HttpPost]
public ActionResult BasicPropertySearch(BasicPropertySearchViewModel viewModel)
{
var predicate = PredicateBuilder.True<ResidentialProperty>();
if (ModelState.IsValid)
{
using(var db = new LetLordContext())
{
predicate = predicate.And(x => x.HasBackGarden);
predicate = predicate.And(x => x.HasFrontGarden);
predicate = predicate.And(x => x.HasSecureParking);
predicate = predicate.And(x => x.IsDisabledFriendly);
var results = db.ResidentialProperty.AsExpandable().Where(
predicate).ToList();
return PartialView("_BasicPropertySearchResultsPartial", results);
}
}
ModelState.AddModelError("", "Something went wrong...");
return View("_BasicPropertySearchPartial");
}
How do I access results in the view if the view the list is being passed to is not strongly typed?
You should use strongly typed view model whenever is possible, however you can use 'dynamic' model in your partial view to access to your data. Or to be more conventionally use the Viewbag dynamic object in your controller and view to pass data:
http://weblogs.asp.net/hajan/archive/2010/12/11/viewbag-dynamic-in-asp-net-mvc-3-rc-2.aspx
Strongly typed views are the preferred method. You can also pass data to the view through the Viewbag object, and reference the Viewbag in the view itself.
I'm totally new at mvc razor, so please excuse my unfamilarity with this subject. If I understood well, it is the controller which gets the url parameter, like the following:
// Get: /User/23
public ActionResult(int? id) {
return View("User", id);
}
So basically, this ActionResult gets the parameter in the url and sends it somewhere. But where?
Also, say I have a repository in which I return all the users in an IQueryable<> class. If the id is not null it gets only the relevant result, if not, it gets the whole list. Therefore, I'd like to return a list to the view and show the results. How can I do that? To ease the understanding I give the following example:
// I want to populate this list based on the id in the actionresult
#foreach(var u in MyList) {
name: #u.Name <br>
lastname: #u.LastName <hr>
}
return View invokes a helper method in the base class, Controller.
It will return a ViewResult, with the parameter being the model passed to the view.
So if you want to pass through a list of User's, you would do this:
public ActionResult Users()
{
var users = _repository.FindAllUsers();
return View(users);
}
Note how i left out the View name, so the default convention will be to look for a View based on the action name, in this case Users.cshtml. Always try and use convention over configuration - this is what makes MVC great.
#model IEnumerable<YourApp.Models.User>
#Html.DisplayForModel()
Shared\DisplayTemplates\User.cshtml
#Html.LabelFor(model => model.Name)
#Html.DisplayFor(model => model.Name)
#Html.LabelFor(model => model.LastName)
#Html.DisplayFor(model => model.LastName)
Note how i didn't use a foreach loop - i use i custom display template, again - convention over configuration.
Whatever you return as the second parameter is the model passed into the view. You could then inherit the razor page in WebViewPage, or better, use the #model keyword. The data passed in is available within the page via the "Model" variable.
Good example of usage after that is at http://weblogs.asp.net/scottgu/archive/2010/10/19/asp-net-mvc-3-new-model-directive-support-in-razor.aspx