I have a view model which have the following two properties:
public List<ContributorModel> Contributors { get; set; }
public Guid ContributorSelected { get; set; }
When setting up the view, the Contributors contains a list of objects, I want to show in a DropDownList. When posting back to the server, I want ContributorSelected to be the value of the DropDownList.
So Inside my form, I've tried:
<div class="formrow">
#Html.DropDownListFor(m=>m.Contributors.First().Id,new SelectList(Model.Contributors,"Id","LastName"))
</div>
But I really have no idea how to format my code so ContributorSelected gets the value. Also, when posting back, the model.Contributors is null.
Any hints?
It's because your model is not accepting the selected contributor, your markup should be:
<div class="formrow">
#Html.DropDownListFor(m=>m.ContributorSelected, new SelectList(Model.Contributors,"Id","LastName"))
</div>
Just make sure the Id is a Guid so it matches Model.ContributorSelected
Related
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.
I am playing with the Scaffolding that is in Asp.net mvc
I have a property on my view model for countries
public IEnumerable<SelectListItem> Countries { get; set; }
Yet, when I create a view and specify the viewmodel it doesn't scaffold a drop down list as I expected it to. In fact it get completely ignored.
I have compiled the project before doing it
I also tried adding a property like this
public int CountryId { get; set; }
As this article suggested there is a convention at work
http://blog.stevensanderson.com/2011/01/28/mvcscaffolding-one-to-many-relationships/
I am using the Add view option you have when right clicking in a controller action method
Any ideas what is wrong?
In my current project, i faced this problem and couldn't find a quick way to scaffold the Dropdown list of a one-many relation inside one of my Entities.
What I ended up doing was like the following:
1- Create the normal AddView=>Create way.
2- If i had a ID property in my model class, the defaul;t template will generate something like this to represent this property in my view:
<div class="editor-label">
#Html.LabelFor(model => model.CityID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CityID)
#Html.ValidationMessageFor(model => model.CityID)
</div>
3- Now I need to replace this default template with a working one, so I wrote this code in the CREATE method:
IEnumerable<SelectListItem> cityItems = dataContext.Cities.AsEnumerable().Select(c => new SelectListItem()
{
Text = c.Name,
Value = c.ID.ToString(),
Selected = false,
});
SelectList cityList = new SelectList(cityItems, "Value", "Text");
ViewBag.CityList = cityList;
this will fetch the Cities table and create a Selectlist that i can pass to my view and work with it to provide the DrobDown with it's items.
4- replace the default code in my View by one like the following:
<div class="Post-label">
<div class="editor-label">
#Html.LabelFor(model => model.CityID)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.CityID, ViewData["CityList"] as SelectList)
#Html.ValidationMessageFor(model => model.CityID)
</div>
</div>
The reason I've used ViewData["CityList"] instead of ViewBag.CityList is that this one worked but the other not.
5- now my view is working find and is fetching the City data just like what I expected, and using the same model inside my Edit view resulted in a working one too.
Give it a try and let me know what happened, Thanks.
I have noticed that for a given model, the "Create" scaffold-generated code when creating a new controller is different than if you right-click in an existing controller and say "Add View" and choose the "Create" scaffolding template. In the first case, given you have the correct properties on the child class
public Country Country {get;set;}
public int CountryID {get;set;}
then this case (adding controller with MVC scaffolding read/write and appropriate Model class) WILL generate a #Html.DropDownList for the parent relationship, whereas right-clicking within the controller Create method will not scaffold the drop-down but will instead create an #Html.EditorFor for the relationship.
So the answer if you want scaffolding code to generate the drop-down may be to delete and re-create your controller if possible, otherwise manually add in the appropriate code.
In order to have the option to choose a country with a dropdown the property in your Model should be:
public Country Country{ get; set; } Navigation property used by EF, doesn't involve the database
with
public Country CountryId{ get; set; }
Create the foreign key on the Person table
Each instance/record of a person is associated with a country: the relation is defined with the navigation property via code and with the CountryID for the database.
The scaffholding template will then generate the edit/create methods and views using :
ViewBag.PossibleCountries
Here's a similar scenario.
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.
Take the example classes below. I want to display the customer and two addresses (from a LIST) on a form. Does the model binder in MVC beta support this or will I have to write my own custom binder?
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Address> Addresses { get; set; }
public Customer()
{
Addresses = new List<Address>();
}
}
public class Address
{
public int Line1 { get; set; }
public int Line2 { get; set; }
public int City { get; set; }
public int State { get; set; }
public int Zip { get; set; }
}
How would you code the fields? Like this?
<!-- some HTML formatting -->
<%= Html.TextBox("customer.address.line1", ViewData.Customer.Address[0].Line1)%>
<!-- some more HTML formatting -->
<%= Html.TextBox("customer.address.line1", ViewData.Customer.Address[1].Line1)%>
<!-- end of HTML form formatting -->
I've never tried it, but see this post, it's about model binding to a list, maybe it can help you.
Use MvcContrib's NameValueDeserializer to make it simpler. Let's assume that your page derives from ViewPage<Customer>. You can do this:
<%= Html.TextBox("Address[0].Line1", ViewData.Model.Address[0].Line1)%>
<%= Html.TextBox("Address[1].Line1", ViewData.Model.Address[1].Line1)%>
And this:
public ActionResult Save([Deserialize]Customer customer)
And the customer will be deserialized from the form post with the address collection populated. Your indexes do not have to be in sequence -- this supports cases where you want to remove rows on the client side before the post occurs.
In the case that you are deserializing something from the view data dictionary (instead of the Model), then the syntax is like [Deserialize("customer")], where "customer" is the prefix.
You might find this blog post interesting and relevant.
Just to make this complete. It's important that you use the hidden fields with the name Index. So my code above becomes this:
<!-- some HTML formatting -->
<%= Html.Hidden("customer.address.Index", 0) %>
<%= Html.TextBox("customer.address[0].line1", ViewData.Customer.Address[0].Line1)%>
<!-- some more HTML formatting -->
<%= Html.Hidden("customer.address.Index", 1) %>
<%= Html.TextBox("customer.address[1].line1", ViewData.Customer.Address[1].Line1)%>
<!-- end of HTML form formatting -->
It works like a charm!
You can pass an object list with ViewData like that, but you need to change some of the lines. Read more here:
http://weblogs.asp.net/scottgu/archive/2007/12/06/asp-net-mvc-framework-part-3-passing-viewdata-from-controllers-to-views.aspx
and here:
ASP.NET MVC: How do I pass a list (from a class in Model) to a repeater in a View?
Hope this helps
Edit
If you use a model, you need to set up the DataContext first and select the list, but sure you can use the classes generated if you use LINQ.
I defined a similar object. I followed the post on binding to a list as referenced above, While the binding works,I was unable to use the Bind whitelist or blacklist in the controller's action parameter. THe model is an IList
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.