How do attributes work in MVC2 - asp.net-mvc

I'm trying to work out exactly what role the attributes play when used on model properties.
For example, If I have a Customer model with a display name attribute set on one of the properties, then I can access the display name attribute value within a display template for whatever reason.
public class Customer {
[DisplayName("Customer Name")]
public string Name { get; set; }
}
/Shared/DisplayTemplates/String.ascx <--- Uses this
<p><%=Model %> | <%=ViewData.ModelMetadata.DisplayName %></p>
-
If however I change the DisplayName attribute to DataType, then MVC looks for a template also called ImageUrl.
public class Customer {
[DataType(DataType.ImageUrl)]
public string Name { get; set; }
}
/Shared/DisplayTemplates/ImageUrl.ascx <---Uses this
<img href="<%=Model %>" /> | <%=ViewData.ModelMetadata.DisplayName %>
Why is the String template being ignored? I thought that MVC matches up the datatypes of the properties to the template names (like the first example) and the attributes are used as metadata within the templates.
It's all very confusing!

MVC matches the datatype of the property when looking for a template - as in your first example.
In your second example, your [DataType] attribute has overriden the string definition, so it now looks for a template named the same as the data type.
If you want to specify which template a property will use you can use the UIHint attribute
public class Customer {
[UIHint("string")]
[DataType(DataType.ImageUrl)]
public string Name { get; set; }
}

Related

An ASP.NET MVC Gotcha? Frustrated

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.

how to show some text in html.LabelFor?

I am doing int like this:
Hello <%= Html.LabelFor(user => user.UserName)%>
But iam getting not a value which is in Property but something strange like this:
Hello User Name,
How can do it to give some value in label out?
Add DataAnnotations to your model/viewmodel:
public class Customer
{
[Display(Name = "Email Address", Description = "An email address is needed to provide notifications about the order.")]
public string EmailAddress { get; set; }
[Display(ResourceType=typeof(DisplayResources), Name="LName", Description="LNameDescription")]
public string LastName { get; set; }
}
Source: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute(v=VS.95).aspx
If you don't provide a display name by the DisplayAttribute then Html.EditorFor(...) uses the properties name, spliting it on upper case letters:
PropertyName --> Label text
Email --> Email
EmailAdress --> Email Address
The reason for this is because Html.LabelFor will do just that - create a label for the property. In this case, it is producing a label of 'User Name' since the property name is UserName.
You just need to look at the model (or whatever your passing to the view) to return the property value: Html.Encode(Model.UserName)
Update (since this was nearly 3 years ago but people have recently upvoted):
You can just use <%: Model.UserName %> to get the HTML encoded value (<%= writes it as raw and <%: writes it encoded).
If you're using a Razor view engine, then #Model.Username will write it out already encoded.

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.

Does the Model Binder in ASP.NET MVC Beta Support List<T>?

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

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