MVC3 IEnumerable of model not finding correct editor template - asp.net-mvc-3

I have the following model:
public class Filter
{
public string Field { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
And the following controller:
public class FilterController
{
public ActionResult Index()
{
IList<Filter> model = new List<Filter>() {
new Filter(),
new Filter()
};
return View(model);
}
}
And the following view:
#model IEnumerable<Filter>
#Html.EditorForModel()
This should look for my EditorTemplate Filter.cshtml, and render the template for each element in the list, right?
Using Glimpse, I notice that MVC is looking for IEnumerable`1.cshtml instead of Filter.cshtml
The same thing happens when I use
#Html.EditorFor(model => model)
When I do this:
#Html.EditorFor(model => model, "Filter")
I get an error saying that Filter.cshtml is expecting a model of type Filter but it received a model of type IEnumerable<Filter>
Am I doing this correctly? Do I need to do anything else to get the list of models to render correctly, using the correct editor template?

I've definitely had issues in the past with EditorTemplates, but I think it was mostly user error.
One possible workaround is to encapsulate your collection in a single, view model class and pass that to the view
public class MySweetFilterViewModel
{
public IEnumerable<Filter> Filters { get; set; }
}
Then you could use a single view to pick apart the collection
#model Project.Models.MySweetFilterViewModel
#Html.EditorFor(x => x.Filters)
Just make sure your controller encapsulates
public ActionResult Index()
{
//...
return View(new MySweetFilterViewModel() { Filters = model });
}

To answer your question regarding why... let's experiment with a few things.
What happens if you wrote your code this way:
return View(new List<Filter>{ new Filter(), new Filter() });
It could be that since you are using an intermediate IList rather than a List, that something is getting confused. What would be happening (in my theory) is that passing an IList<Filter> is causing a standard IEnumerable rather than an IEnumerable<Filter> to be passed to the view.
You could also try model.AsEnumerable<Filter>()

Related

MVC3 Razor Editor/Display templates and generics

There were a few questions regarding mvc templates and generics however there seems to be none related to what I'm looking for. Consider the following models:
namespace MyNamespace
{
public class ModelBase { /* Not important for example */ }
public class MyModel : ModelBase
{
public string Name { get; set; }
}
public class MyViewModel
{
public IEnumerable<ModelBase> Data { get; set; }
}
}
And a controller:
public class HomeController : Controller
{
public ActionResult Index
{
return View(new MyViewModel { Data = new List<MyModel>() })
}
}
A Razor view Views/Home/Index.cshtml would look like:
#model MyNamespace.MyViewModel
#Html.EditorFor(m => m.Data)
Nothing special there. If I want a display or editor template for that I can create a file under Views/Shared/EditorTemplate or under Views/Home/EditorTemplates called MyModel.cshtml and it finds it correctly.
What if I want to do something different for each implementation of ModelBase when showing a list? Mvc view finder will find List'1.cshtml template correctly in any of above paths. However what I need to do is do a template for List`1[MyModel].cshtml
I can't seem to get the correct file naming. What I've tried so far (relative to this example naming):
List`1[MyModel].cshtml
List`1[[MyModel]].cshtml
List`1[MyNamespace.MyModel].cshtml
List`1[[MyNamespace.MyModel]].cshtml
If possible I want to avoid writing a custom view finder. The only alternative I can think of for now if I can't get above stuff to work is to just have List`1.cshtml call a partial with naming deduced from List.
A very late response, useful if someone else bump in this very same question (as I did a few moments ago trying to remember how to do this)
You can use the UIHintAttribute to define the name of the editor
public class MyViewModel
{
[UIHint("MyModel")]
public IEnumerable<ModelBase> Data { get; set; }
}
I haven't checked this code but I would create different Views for each subtype and do something dumb like:
return View(MyModel.GetType().Name, new MyViewModel { Data = new List<MyModel>() })
So that your View matches the name of your type.
You could do this in the main view:
#model MyViewModel
#Html.EditorFor(x => x.Data)
and then have:
~/Views/Shared/EditorTemplates/MyModel.cshtml:
#model MyModel
...
~/Views/Shared/EditorTemplates/MyOtherModel.cshtml (where obviously MyOtherModel derives from ModelBase):
#model MyOtherModel
...
and so on ... ASP.NET MVC will take care of looping through the Data property and pick the correct template based on the runtime type of each element of this collection.

asp.net mvc3 updated (refresh) the viewmodel in view

I send a BOOKVIEWMODEL with fields and a simple IEnumerable in view I get the this list IEnumerable in the view by a method with JSON AJAX in view and I fill my table Ristourne(View) with JQUERY it works very well but I not know how I fill (BIND or refresh) the list IEnumerable of my BOOKVIEWMODEL in the VIEW to recovered it in the Controller:
[HttpPost]
public ActionResult Create(BookViewModel _bookViewModel)
{
if (ModelState.IsValid)
{
_bookViewModel.Ristourne
return RedirectToAction("Index");
}
return View(_bookViewModel);
my bookviewmodel
public class BookViewModel
{
public String book { get; set; }
public String price { get; set; }
public IEnumerable<Ristourne> Ristourne { get; set; }
}
For the model binding to work, you need to "mimic" the convention MVC uses when generating the form fields.
I don't know the contents of the Ristourne class, but let's say it had 1 field called Foo.
In that case, when you render out the elements (from the JSON AJAX callback), make them look like this:
<input type="text" name="Model.Ristourne[0].Foo" id="Model.Ristourne[0].Foo"/>
<input type="text" name="Model.Ristourne[1].Foo" id="Model.Ristourne[1].Foo"/>
<input type="text" name="Model.Ristourne[2].Foo" id="Model.Ristourne[2].Foo"/>
And so on. Easiest thing to do is in your AJAX callback, just use a basic for loop to create the elements indexer.
Altenatively, a cheat/easy way around this problem would be to make your AJAX action return a PartialViewResult:
public PartialViewResult Book()
{
var ristournes = _repo.Get();
var model = new BooksViewModel { Ristourne = ristournes };
return PartialView(model);
}
Then the partial view:
#Html.EditorFor(model => mode.Ristourne)
Then MVC will create the form fields correctly.
I always prefer this option over dynamically generated form fields. If you want to go down this path often, you should consider something like Knockout.js and/or Upshot.

html.TextBoxFor and html.Textbox, POSTing values, model in parameters

Alright guys, Need some help!
Im working with asp.net mvc3 razor (and am fairly new to it but did lots of web forms)
Okay so onto the problem
My question revolves around submitting a view.
I have a very complicated model that my view is based off (strongly typed).
I want to return the model into the arguments in the HttpPost method of the controller. do basically:
public ActionResult Personal()
{
DataModel dataModel = new DataModel();
FormModel model = new FormModel();
model.candidateModel = dataModel.candidateModel;
model.lookupModel = new LookupModel();
return View(model);
}
[HttpPost]
public ActionResult Personal(FormModel formModel)
{
if (ModelState.IsValid)
{
//stuff
}
return View(formModel);
}
Now...
I'm having trouble getting values into the formModel parameter on the post method.
This works (meaning i can see the value)but is tedious as i have to write exactly where it sits in a string every single field:
#Html.TextBox("formModel.candidateModel.tblApplicant.FirstName", Model.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="formModel.candidateModel.tblApplicant.FirstName" id="formModel_candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
This doesn't work:
#Html.TextBoxFor(c => c.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="candidateModel.tblApplicant.FirstName" id="candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
Now I'm assuming the problem lies in the discrepancy of the id's
So please answer me this:
Am i going about this the right way
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
Additionally, it seems that textboxfor is restrictive, in the manner that if you have a date time, how do you use the .toshortdate() method? This makes me think textboxfor isn't useful for me.
Quick clarification:
when i say textboxfor isn't working, it IS getting values when i GET the form. So they fill, but on the POST / submission, i can't see them in the formModel in the parameters.
Another side note:
None of the html helpers work, this is the problem. They aren't appearing in modelstate either.
Thanks everyone for the help
Answer:
html.TextBoxFor and html.Textbox, POSTing values, model in parameters
It was a problem in my view somewhere, i replaced all the code with the snippet in this answer and it worked.
Thank you again
Am i going about this the right way
Yes.
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
There is something else in your code that makes this not work. It's difficult to say since you haven't shown all your code. Here's a full working example which illustrates and proves that there's something else going on with your code:
Model:
public class FormModel
{
public CandidateModel candidateModel { get; set; }
}
public class CandidateModel
{
public Applicant tblApplicant { get; set; }
}
public class Applicant
{
public string FirstName { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new FormModel
{
candidateModel = new CandidateModel
{
tblApplicant = new Applicant
{
FirstName = "fn"
}
}
});
}
[HttpPost]
public ActionResult Index(FormModel formModel)
{
// the username will be correctly bound here
return View(formModel);
}
}
View:
#model FormModel
#using (Html.BeginForm())
{
#Html.EditorFor(c => c.candidateModel.tblApplicant.FirstName)
<button type="submit">OK</button>
}
Additionally, it seems that textboxfor is restrictive, in the manner
that if you have a date time, how do you use the .toshortdate()
method? This makes me think textboxfor isn't useful for me.
I agree that TextBoxFor is restrictive. That's why I would recommend you always using EditorFor instead of TextBoxFor. It will allow you to simply decorate your view model property with the [DisplayFormat] attribute and voilĂ . You get any format you like.
For example:
public class MyViewModel
{
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime CreatedAt { get; set; }
}
and in the view:
#model MyViewModel
#Html.EditorFor(x => x.CreatedAt)
will format the date exactly as you expect.
the model binder uses the name to bind the values to the model, and the html helpers e.g. Html.TextBoxFor uses the body of the lambda expression to set the name, however you can specify the name yourself which you are doing by using the Html.TextBox( helper
#Html.TextBoxFor(x=>x.candidateModel.tblApplicant.FirstName),
new{#Name="formModel.candidateModel.tblApplicant.FirstName"})
If your view is strongly typed, try the helper bellow, instead call each helper on each property
#Html.EditorForModel()
#Html.EditorFor(m => m.candidateModel)
#Html.EditorFor(m => m.lookupModel)
Update:
Well, have tried to use viewmodel to simplify this task? And when you get back the data you can map your real models. keep your views clean will give you less headaches in the future. Additionally you could use AutoMapper to help you.
Here a example if you think that will help you.
http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx

MVC Asp.net How to pass a list of complex objects from the view to the controller using Actionlink?

I have a model which contains a list of another model.
Let's say I have a MovieModel:
public class MovieModel
{
public int MovieId { get; set; }
public string Title { get; set; }
public string Director { get; set; }
}
Then I have the RentalModel:
public class RentalModel
{
public int RentalId { get; set; }
public string CustomerId { get; set; }
public List<MovieModel> Movies { get; set; }
}
Then I have a place where all the rentals are displayed, which by clicking on the rental, its details will be displayed, from the "ShowRentals.aspx" to "ShowRentalDetails.aspx"
<% using (Html.BeginForm()) { %>
<% foreach(var rent in Model) { %>
<div class="editor-label">
<div class="editor-field">
<%: rent.RentalId %>
<%: Html.ActionLink("Details", "ShowRentalDetails",
new {rentalId = rent.RentalId,
customerId = rent.CustomerId,
movies = rent.Movies,
})%>
When I debug, I see that the Movies list is always null. This is because only primitive parameters are passed successfully, such as the Ids. I was never able to pass complex types. I really need this list to be passed on to the controller. Is it maybe because the actionlink is not capable? What other work-arounds can I do? I've been stuck on this for a while.
Nevermind the bare code here, this is just to show you what I'm doing with the list. Please help.
(follow up)
In the Controller, here's the two actions, ShowRentals and ShowRentalDetails:
public ActionResult ShowRentals()
{
MembershipUser user = Membership.GetUser(User.Identity.Name, true);
Guid guid = (Guid)user.ProviderUserKey;
Entities dataContext = new Entities();
Member member = dataContext.Members.Where(m => m.UserID == guid).First();
IEnumerable<RentalModel> toReturn = from r in member.Rentals
select new RentalModel
{
RentalId = m.RentalID,
CustomerId = m.CustomerID,
};
return View(toReturn);
}
[Authorize]
public ActionResult ShowRentalDetails(RentalModel model, List<MovieModel> movies)
{
return View("ShowRentalDetails", model);
}
I can't set it in ShowRentals because the array of movies in the database is of Movie type and not MovieModel, so the two lists are not compatible. It is null in the model when passed from ShowRentals view and the model is reconstructed by mvc, and it also doesn't work when explicitly passed from the actionlink as a parameter. help!
I believe Html.ActionLink performs a GET and you can't pass complex data types using a GET.
If you could refetch the movie list in your ShowRentDetails controller by using the rental id I think that would be best.
Otherwise, you could look up EditorFor templates. If you make an editorfor template for MovieModel and post a RentalModel to ShowRentDetails then you could get the MovieModel list that way.
See http://weblogs.asp.net/shijuvarghese/archive/2010/03/06/persisting-model-state-in-asp-net-mvc-using-html-serialize.aspx for another way.
On a side note, theres no need to make
List<MovieModel> movies
a second parameter in ShowRentDetails when it's already included in the model
Source: ASP.NET MVC - Trouble passing model in Html.ActionLink routeValues
It is clear you cant pass complex view models through action link. There is a possibility to pass simple objects which does not have any complex properties. There is another way you can do as multiple submit buttons and do a post to controller. Through the submit you have possibilities to post complex view models

passing form data to controller mvc3

I'm brand new to .net MVC3 so pardon my ignorance. I have a relatively large form (lots of fields) and I'm just wondering if I really need to reference each one of my fields as arguments to my action method on the back end or if it's possible to pass them all in as some sort of collection then reference the collection to obtain the values.
If that's possible could someone please provide a short example of how?
thanks
Shortest example I can come up with...
View model:
public class ViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
View:
<%: Html.EditorForModel() %>
Controller
[HttpGet]
public ActionResult Person()
{
return View(new ViewModel());
}
[HttpPost]
public ActionResult Person(ViewModel formData)
{
// formData is bound already -- just use it!
}
You can pass all the data to the controller as a custom type.
public ActionResult MyControllerMethod(MyCustomType formData)
If you strongly type your view then you'll be able to render the form fields using the HtmlHelper such as:
<%= Html.TextBoxFor(m => m.FirstName) %>
This was the ID of the form fields, which is used to associate the form field with the model property, will already be set for you.

Resources