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.
Related
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
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.
Simple question on best practice. Say I have:
public class Product
{
public string Name { get; set; }
public string Price { get; set; }
public int CategoryID { get; set; }
public bool IsAvailable { get; set; }
}
and i have a view using IEnumerable< Product> as the model, and i iterate through the Products on the page and want to show the total of the prices at the end of the list, should I use:
<%= Model.Sum(x=> x.Price) %>
or should I use some other method? This may extend to include more involved things like:
<%= Model.Where(x=> x.CategoryID == 5 && x.IsAvailable).Sum(x=> x.Price) %>
and even
<% foreach (Product p in Model.Where(x=> x.IsAvailable) {%>
-- insert html --
<% } %>
<% foreach (Product p in Model.Where(x=> !x.IsAvailable) {%>
-- insert html --
<% } %>
I guess this comes down to should I have that sort of code within my view, or should i be passing it to my view in ViewData? Or perhaps some other way?
If the logic you are using on the page is related to the display of data on the page, then I don't have a problem using logic that does calculations. Calculating the values in your controller and supplying it as part of the model would couple the display to the action performed by the controller, i.e., if you simply want to change how the data is displayed -- for example, group by category and show sub-totals -- and expected all the data to be in the model, then you'd have to touch both the controller and the view to make the change. If you put the display-related calculations in the view, then only the view needs to change.
The call on whether the logic is business-related or view-related is heavily context dependent. For example, you might have a business rule that says you only display products that are available. Enforcing this rule certainly shouldn't be a function of the view so, in that case, you should move it into the controller (or even the model). But if it's a simple calculation of the contents of a shopping cart or filtering what you display based on model properties, I'd be ok with it in the view.
I would push this logic back into the controller and the "view model". I consider this to be the "model of the view". A simple example would be
return View(new ProductSummaryViewModel
{
Products = products,
TotalPrice = products.Sum(p => p.Price)
});
You can obviously extend this for whatever the view has to display. You can even have collections of sub-viewmodels which can make things a lot easier.
Hope this helps
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.