An ASP.NET MVC Gotcha? Frustrated - asp.net-mvc

I have an issue with ASP.NET MVC html helpers like TextBoxFor(), HiddenFor(), etc. If I have a model such as Employee with the string member Name and execute Html.TextBoxFor(p => p.Name), is it wrong for me to assume that ASP.NET MVC will always use the value in the Employee's Name? Because it doesn't. ASP.NET will override that binding and use what's in the POST.
For exmaple let's say I have the following code:
Model
namespace MvcApplication2.Models
{
public class Company
{
public string Name { set; get; }
public List<Employee> Employees { set; get; }
}
public class Employee
{
public string Name { set; get; }
}
}
Controller
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
public ActionResult Company(string Name)
{
return View(new Company {
Name = Name,
Employees = new List<Employee> {
new Employee { Name = "Ralph" },
new Employee { Name = "Joe" } }
});
}
}
}
Home/Company.cshtml
#using MvcApplication2.Models;
#model Company
<h2>Company's Name: #Model.Name</h2>
#foreach (Employee emp in Model.Employees)
{
Html.RenderPartial("Employee", emp);
}
Home/Employee.cshtml
#model MvcApplication2.Models.Employee
<b>Employee's Name: </b> #Html.TextBoxFor(p => p.Name);
When I hit the relative url "Home/Company?Name=MSFT", I expected Employee.cshtml to render "Ralph" and "Joe" in the textboxes but instead it renders MSFT for both textboxes. What do I have to do so that "Ralph" and "Joe" shows up in the textboxes? Do i have to make sure that my POST and GET variables never conflict in all layers of my view models (in this case the Company and Employee classes)? This seems silly. There's got to be an easy workaround, right?
Here's a screenshot of the result:

HtmlHelper methods and ModelState
An important thing to understand with MVC HtmlHelper methods:
They always look at ModelState first, value second, ViewData third.
The ModelState is important, because it contains the user-submitted values. If a page fails validation, the ModelState is used to store the previous values and error messages.
If you want to POST a form, and if everything is valid, you want to show the form again, you either have to:
Redierect from the POST to the GET (following the PRG Pattern)
Clear the ModelState: ModelState.Clear();
EditorFor vs RenderPartial
Another important thing to mention is the difference between EditorFor/DisplayFor vs RenderPartial.
When you use EditorFor/DisplayFor for an item (such as .EditorFor(m => m.Person)), it adds a sort-of "namespace" to the template of "Person" so that the sub-controls will have a unique name. For example, in the template, .TextBoxFor(p => p.Name) will render something like <input name="Person.Name" ....
However, when you use RenderPartial, no such namespace is created. Therefore, .TextBoxFor(p => p.Name) will render <input name="Name" ..., which will not be unique if you have multiple editors.

I understand that this behavior might be frustrating, and that it might seem illogical. However, your frustration is a result of not fully understanding how Model binding works. There is a logical chain of events that occur during binding, and if you understand how it works then it will help avoid these kinds of mistakes.
As Scott mentioned, MVC will always look in ModelState first. You can avoid this problem by making sure you don't have any querystring parameters that have the same name as any properties in your model.

Related

ASP.NET MVC 5 model validation for non-nullable types (Int32)

I'm working on an ASP.NET MVC 5 application and the project owner is concerned about "under-posting" issues caused by validating non-nullable types (as mentioned in http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html and http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api).
I created a test case to replicate this issue in ASP.NET MVC 5 but without luck.
Model:
public class ContactModel
{
[Required]
public Int32 data1 { get; set; }
public Int32 data2 { get; set; }
}
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
</div>
</div>
<div>
#Html.LabelFor(model => model.data2)
<div>
#Html.EditorFor(model => model.data2)
</div>
</div>
Controller:
public ActionResult Index(Models.ContactModel contact)
{
if (ModelState.IsValid)
{
Response.Write("modelstate is valid<br>");
return View();
}
else
{
Response.Write("modelstate is invalid<br>");
return View();
}
}
It seems that when data1 and data2 are null in the post, their values in the model (contact) will be 0. However, ModelState.IsValid will also be false (instead of true as shown in the two articles).
What I have:
What the second article showed:
I couldn't find any information regarding changes on how model validation works in ASP.NET MVC, so I'm guessing I did something wrong with my test case. Any thought and suggestion are appreciated.
The reason your ModelState is false is because the post is providing form values from each property in your model. Essentially the Model binding system is checking the validity of both data1 and data2 fields as you have #Html.EditorFor helpers explicitly written for both properties in your view (so no underposting is actually going on).
I did successfully replicate the under-posting concerns from the articles. Simply remove one of the EditorFor helpers in your view, so you're actually underposting. With both helpers present, there's no underposting going on. So the view looks like this now (note I added the validation helper for both properties to get feedback in the view on what's going on):
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data2)
</div>
</div>
Make sure to leave the #Html.EditorFor helper completely off for the data2 property. Now fill in zero in the form field (you'll only one form field in the view of course now), and post to your action.
ModelState will come back as true in this scenario, even though only one form field is being posted. Not a good result if someone does underpost! So here's the (slightly modified) original model class where underposting issues will occur in the case a form field is left off of your form (note the Required attributes don't make any difference in this situation as both properties are value types):
//You could add the Required attribute or not, doesn't matter at this point.
//The concern here is that the Modelstate will still come back as Valid
//in the case of a form field being left off of your form (or someone underposts).
//So to replicate underposting issues, make sure to comment or delete
//at least one Html.EditorFor helper in the view.
//[Required] Underposting will occur regardless if this is marked required or not,
//so be careful if someone does underpost your form.
public Int32 data1 { get; set; }
//[Required]
public Int32 data2 { get; set; }
Now the solution if you want to solve the underposting issue:
Simply mark both properties as required and make them nullable as mentioned in the articles you provided, like so:
[Required]
public Int32? data1 { get; set; }
[Required]
public Int32? data2 { get; set; }
Now when the view is posted with a missing #Html.EditorFor helper or a missing form field, the ModelState Validation will come back as false, and you're protected from underposting issues.

Pass an entire model on form submission

I understand that I can use #Html.HiddenFor(m => m.parameter) and when the form is submitted, that parameter will be passed to the controller. My model has many properties.
Is there a shorter way of passing the entire model at once to the controller or must I do it one by one each time?
The model will be passed to the controller in its entirety, but the values of properties that are not bound by input or hidden fields will be lost.
You have to either bind the properties in the form on the client-side, or re-fetch the entity on the server-side.
You seem to be asking for something like #Html.HiddenFor(m => m.Model), and that is not possible. Sorry
One thing to keep in mind, if you have tons of hidden fields, you may be sending more data to the view than you really need. Consider employing view models
For anyone else who looks at this you can do a #Html.EditorForModel() in a hidden div. You'd also have to use #Html.EditorFor(model => model.ObjectProperty) for each object property of the model.
<div hidden="hidden">
#Html.EditorForModel()
#Html.EditorFor(model => model.ObjectProperty)
#Html.EditorFor(model => model.ListOfObjectsProperty)
</div>
The entire model will be posted if you are using a FORM element. Your elements using the Model obviously need to be inside the form element
You can also POST the form yourself say by using JQuery
See this other stack issue for that : jQuery AJAX submit form
Have a close look at the anwser by "Alfrekjv"
This is already built in. Consider this model:
public class MyModel
{
public string PropertyA { get; set; }
public string parameter { get; set; }
}
and now consider this action:
[HttpPost]
public ActionResult PostSomeData(MyModel model)
{
}
MVC will leverage the FormCollection and fill in the MyModel class where it can. If you don't have the PropertyA in the form then it will be null. But since you have an input for the parameter property it will be filled in.
You can check only the properties you want:
if (this.ModelState.IsValidField("Name"))
{
// .....
}
instead of:
if (this.ModelState.IsValid)
{
// .....
}
#using (Ajax.BeginForm("my_function", "my_controller", new AjaxOptions { InsertionMode = InsertionMode.Replace }, mymodel))

Partials With View Data Object

I have the following ViewData that I pass into a view.
public class MerchantSignUpViewData : BaseViewData
{
public Merchant Merchant { get; set; }
public Address Address { get; set; }
public Deal Deal { get; set; }
public List<MerchantContact> Contacts { get; set; }
public int TabIndex { get; set; }
public List<DealPricing> DealPricing { get; set; }
}
I also created 3 partial views. Merchant Info, Address, Merchant Properties
In my View I have a Deal Model that shares the same field names as Merchant which is "Name"
I can't put these in the same form cause the names will be the same.
What I ended up doing was putting all 10 partial views into one huge form (I started crying at this point) and bound like this.
<%: Html.TextBoxFor(model => model.Deal.Name)%>
<%: Html.TextBoxFor(model => model.Deal.Name)%>
This gives me the correct names of the form elements.
What I want to do is the following.
<% Html.RenderPartial("MerchantForm", Model.Merchant) %>
<% Html.RenderPartial("DealForm", Model.Deal) %>
But how do I add a prefix to all the TextBoxFor pieces or preferable the render partial tags.
Hope I provided enough information or maybe I'm just doing this the wrong way. Either will help me in the long run so thanks in advance.
Maybe I'm not quite getting the problem but I think this is exactly what Html.EditorFor(x=>x...) is for.
Create a folder called "EditorTemplates" in the same directory where your views are. Put your partials in here and give them the same name as your model type (eg rename "MerchantForm.ascx" to "Merchant.ascx").
In your main view instead of
Html.RenderPartial("MerchantForm", Model.Merchant)
use
Html.EditorFor(x=>x.Merchant)
The templating system will deal with the prefixes for you so that on post the model binder will tie everything up properly.
If you have templates set up for all the complex objects in the model you can even go a step further and on your main view just call
Html.EditorForModel()
Which will reflect over the properties in your model and call their relevant editors.

Binding, Prefixes and generated HTML

MVC newbie question re binders. Supposing I have two strongly typed partial actions that happen to have a model attributes with the same name, and are rendered in the same containing page i.e.:
Class Friend {string Name {get; set ;} DateTime DOB {get; set ;}}
Class Foe {string Name {get; set ;} string ReasonForDislike {get; set ;}}
Both partials will have a line:
<%= Html.TextBoxFor(model => model.Name) %>
And associated controller actions:
public ActionResult SaveFriend(Friend friend)
public ActionResult SaveFoe(Foe foe)
My problem is that both will render on my containing page with the same id (of course, bad for lots of reasons). I’m aware of the [Bind] attribute that allows me add a prefix, resulting in code:
public ActionResult SaveFriend([Bind(Prefix = “friend”)] Friend friend)
<%= Html.TextBox("friend.Name", Model. Name) %> //Boo, no TextBoxFor :(
But this still doesn’t cut it. I can just about tolerate the loss of the strongly typed TextBoxFor helpers but I’ve yet to get clientside validation to work with prefixes:
I’ve tried:
<%= Html.ValidationMessage("friend.Name") %>
...and every other variant I can think of.
I seem to need the model to be aware of the prefix in both directions but bind only applies when mapping the inbound request. It seems (to me) a common scenario but I’m struggling to find examples out there. What am I missing!
Thanks in advance.
The prefix is there so you can wrap your objects in an "outer" ViewModel.
Suppose we have:
public class MyViewModel
{
public Friend friend;
public Foe foe;
}
If you use this class as your ViewModel and as the base of your strongly-typed Views, then your strongly-typed Textboxes will be named thusly:
friend.Name
foe.Name
You can then use the Prefix attribute you refer to in your question to disambiguate between your Friend and Foe classes.

How do I add relational data to an ASP.net MVC data entity Create view?

Let's say I have a table of staff members, and a separate table of staff roles in a many-to-many relationship (ie, with a StaffMembersInRoles table in between). Using the ADO.net entity framework we have an object model like this:
alt text http://martindoms.com/img/datamodel.png
I have a StaffController controller with a create method and a Create view which is pretty much the standard automatically generated type. I want the create page to list the roles in the StaffRoles table with a checkbox next to each, and when the submit button is pressed these are added to the new StaffMember StaffRole IEnumable (and so the appropriate entries are made in the StaffMembersInRoles table). How would I go about this with a strongly-typed view?
This is how i'd do it:
Firstly, you need an array of all possible roles.
In the controller, i'd do something like this (i'm making some assumptions about your DAO):
ViewData["AllRoles"] = (StaffRole[])StaffRole.FindAll();
Then in your view, loop through the roles:
<% foreach (StaffRole Role in (StaffRole[])ViewData["AllRoles"]) { %>
<p>
<label>
<%= Html.CheckBox("Role_"+Role.RoleId.ToString()) %>
<%= Html.Encode(Role.RoleName) %>
</label>
</p>
<% } %>
Then in your POST controller, do something like this:
foreach (StaffRole Role in (StaffRole[])StaffRole.FindAll())
{
if (Request.Params["Role_"+Role.RoleId.ToString()]=="true")
MyStaff.Roles.Add(Role);
}
Hi the Problem with this approach is you don't really have a strongly type entity passed to the View. In this problem you need the StaffMember information and a list of all StaffRole entities. PS: I really dun like the approach of casting the list in the view : StaffRole[])ViewData["AllRoles"]
Basicly i will prefer to work with DTO.
DTO:
public StaffMemberDto
{
public int StaffMemberId { get; set; }
public IList<StaffRoleDto> AllStaffRoles { get; set;}
public IList<StaffRoleDto> MembersRolesAttached { get; set;}
}
public StaffRoleDto
{
public int RoleId {get; set;}
public string RoleName { get; set; }
}
Controller:
return View(StaffMemberDto);
So in the view you get all roles strongly typed:
foreach (var role in ViewDate.Model.AllStaffRoles)
{
...
}
And in the post you can send the StaffMemberDto with the good RoleDto already assigned in the view or you can do the Request trick to get the id of the checkboxes ticked.
Well in a view like this i will probably use jquery to request an addRole each time someone tick the box to add a role. It will add some ajax to your form and you will not have some postback.

Resources