I am trying to work around the fact that when they wrote asp.net MVC 3 they forgot to include code to add the unobtrusive validation attributes to select lists and their "fix" for this is to include it in MVC 4, which is no bloody use to anyone using MVC 3.
My proposed work around is to use Html.GetUnobtrusiveValidationAttributes() to add them myself, just like any other custom attributes, but i can't work out the correct syntax for calling the method. There are 2 overloads, one takes a string and the other takes a string and a ModelMetaData class. I understand the metadata param, I presume I just pass in ViewData.ModelMetadata but what should the string be? The MSDN documentation says it is "the specified HTML name attribute" which makes no sense to me. The HTML name attribute of what? The select list? Why would it need that and how does that help it know what property on my model i want the validation for? Looking at examples of usage they all seem to pass in the name of the property on my model that i want the validation attributes for, which makes sense. Unfortunately I can't get the method to return anything but an empty collection no matter what i pass in.
My model class is called Event and my property is called EventTypeID. I am using a slightly different viewmodel class as the basis for the view because i need to display a list of Events and also also allow a new event to be entered on the same view so i have a simple viewmodel class as below:
public class EventViewModel
{
public Model.Event NewEvent { get; set; }
public IEnumerable<Model.Event> Events { get; set; }
}
The dropdown list is mapped to the property like: #Html.DropDownListFor(model => model.NewEvent.EventTypeID what do I pass as the string to Html.GetUnobtrusiveValidationAttributes(string) or Html.GetUnobtrusiveValidationAttributes(string, ModelMetadata) to get the attributes for this property. I have tried:
Html.GetUnobtrusiveValidationAttributes("EventTypeID")
Html.GetUnobtrusiveValidationAttributes("EventTypeID",ViewData.ModelMetadata)
Html.GetUnobtrusiveValidationAttributes("NewEvent.EventTypeID")
Html.GetUnobtrusiveValidationAttributes("NewEvent.EventTypeID",ModelMetadata)
They all return an empty collection.
I know that my model is correct because if i change the call from Html.DropDownListFor to Html.TextBoxFor then the validation "just works" without me having to do anything other than add the validation attributes to my model class.
EDIT:
Just tried turning client side validation off, the validation works fine server side for all select lists.
For those still looking for an answer, this works for me:
public static IDictionary<string, object> UnobtrusiveValidationAttributesFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> propertyExpression)
{
var propertyName = html.NameFor(propertyExpression).ToString();
var metadata = ModelMetadata.FromLambdaExpression(propertyExpression, html.ViewData);
var attributes = html.GetUnobtrusiveValidationAttributes(propertyName, metadata);
return attributes;
}
Note that I'm using .Net MVC 4, you don't have the html.NameFor method in MVC 3. However, I believe this can be done in MVC 3 with the following method:
var propertyName = ExpressionHelper.GetExpressionText(propertyExpression);
You can use it inline
Example for select element
<select name="#Html.NameFor(m=> m.MyProperty)"
id="#Html.IdFor(m=> m.MyProperty)"
#Html.Raw(string.Join(" ", Html.GetUnobtrusiveValidationAttributes(Html.NameFor(m => m.MyProperty).ToString()).Select(x => x.Key.ToString() + "=\"" + x.Value + "\"")))
>
Here is a link to an answer I posted, showing an HtmlHelper I wrote to provide unobtrusive validation for dropdownlists: MVC 3 dropdownlist validation not working for complex view model
UPDATE
Are you trying to get the attributes in an HtmlHelper, or in-line in your view?
Assuming you are trying to get the attributes in your view, that is the problem.
First, you need to understand that ModelMetadata does not represent a single object available across your entire model. Rather, it represents the metadata for a particular element, be it your model, or any property within the model. A better descriptive name would be ObjectMetadata, since ModelMetadata is the metadata for a specified object, be it a model, a nested model, or a specific property.
ModelMetadata in the view is only the metadata for the top-level model. You must get the ModelMetadata for the property to which the dropdownlist is bound. If you use a helper, then the helper is passed the correct ModelMetadata as a matter of course. If you use your view, you need to engage in some gymnastics to get the correct ModelMetadata, see for example my answer here: Validating and editing a “Changeable”/optional type in Asp.net MVC 3
Related
I have an add product form which depends upon the user having added categories, product types etc. previously. Rather than let them complete the form, and then realise that they can't submit it, I want to show an error when the form first loads.
I want to add this error to the modelstate against the appropriate property - so my question is how can I get the modelstate key name for that property?
Code
Model.Step1.PopulateData(_productTypeSvc.GetList(), _websiteSvc.GetMaximumNumberOfProductImages(HttpContext.Request.Url.Host));
Model.Title = "Add A Product - Step 1: The Basics";
if (Model.Step1.ProductTypes.IsNullOrEmpty())
{
//Rather than string.Empty, I want to programmatically get the correct key to add the error to..
//E.g. if it were the view - HtmlHelper.NameFor(m => m.Step1.ProductTypeID)
ModelState.AddModelError(string.Empty, "Please add at least one product type before adding products.");
}
I know I can dummy the HtmlHelper object, but I'd rather avoid this and wondered if there is a better way?
Found an appropriate extension in MVC:
System.Web.Mvc.ExpressionHelper.GetExpressionText(LambdaExpression expression);
MSDN Here.
Thanks anyway guys.
Update:
public static string GetPropertyName<TModel, TValue>(this TModel model, Expression<Func<TModel, TValue>> propertySelector)
{
return ExpressionHelper.GetExpressionText(propertySelector);
}
Then use the extension:
ModelState.AddModelError(Model.GetPropertyName(m => m.Step1.ProductTypeID), "Please add at least one product type before adding products.");
I am a newbie to MVC 4, (after 10 yrs of webforms) and have a question that I have not been able to figure out.
When writing code in the cshtml file, I am walking through a tutorial that has the following line:
#Html.DisplayNameFor(model => model.City)
What does the model => model.City imply? Why can't I use #Html.DisplayNameFor(model.City) ? I understand this is Linq query, but I would like to understand why would I need the model goes to model.city ?
Generally, that is called a lambda expression.In your scenario, you are telling the DisplayNameFor method that "take my model, and create a display element for this property.".You can't use model.City, because it just returns the value of the property.The method needs more than that in order to create a display element for your property.For example, it needs to know it's type and also it's attributes (like DisplayName attribute) and then it creates a display element for your element(it should be label I guess) .
DisplayName method is doing that using Expression Trees.The method takes an Expression<Func<TModel, TValue>> and uses it to get the name, value and the metadata information (attributes) about your property.
If you want to use model.City you can still use it, but then you won't need the functionality that DisplayNameFor provides.If you just need to display value of the property you can always do it like this:
<label> #model.City </label>
I understand this is Linq query,
Btw, this is incorrect, that is not a LINQ query.That is just an extension method.
I'm struggling with the following problem and I can't find an acceptable way to solve it.
My challenge: write out HTML comments just before the actual property value in a Razor view.
This is my (simplyfied) Viewmodel:
public class Article
{
public string Title {get;set;}
}
To write out this title I simply do this in my Razor view:
<h2>#Model.Title</h2>
Now I want to write out a html comment just before the actual title so the generated HTML looks like this (simplyfied):
<h2><!-- some parameters for a 3th party system --> This is my title</h2>
The HTML comment comes from an Attribute I applied to the 'Title' attribute. It's value is generated, so the attribute-value is added at runtime using the TypeDescriptor from the .NET framework.
Now I know I could achieve this by simply writing out all my properties using an HTML helper. Like this: #MyHelper.Write(m => m.Title)
But since potentially ALL my properties need this HTML comment I want to avoid the use of an HTML helper since it clutters the View and doesn't make the view look nice and (more) readable.
This is what I have tried:
Created a custom Razor base page (Inheriting from WebViewPage<TModel>). And overwriting it's 'Write' method.
This kind of works but the BIGGEST problem here is that I don't know which property is been written out at that moment. There is no way of getting the current property name in the 'Write' method. So now I dynamically search my Model to find a property with the value that's been written out and prepend the HTML comment from the attribute.
My question: is there another approach to accomplish what I want. As sais before: I want to avoid using an HTML helper to write out all my properties. (Think about loops, etc. It's just not nice).
Also, adding this HTML comment in my Controller is no option since:
it's not part of the actual value. Is a sort of metadata.
The HTML comment should be added to int's, double's and DateTime's. There is no way to adjust a double property to include a string. (Image a List<DateTime>. All date's need this HTML comment)
the HTML comment should be added based on a web.config setting. Yes or No. (The actual HTML comment is different for each value of a property)
I realize this question is rather long. Sorry for that. Any thoughts are appreciated.
You can use the existing #Html.Raw(Model.Title)
Alternatively you can use a display templates. Add a UIHintAttribute to the properties you wish to behave this way.
public class MyModel
{
[UIHint("Raw")]
public string MyString { get; set; }
}
Create a new display template called Raw.cshtml that accepts model of type string:
#model string
#Html.Raw(model)
Then in your view you can use:
#Html.DisplayFor(m => m.MyString)
This still requires that you use a helper (DisplayFor). This is a recommended practice that allows you to easily change the behavior of one or many fields with minimal code changes.
I'm trying to create a strongly typed model for one of my Views in MVC. The model is the result of a LINQ GroupBy query so it is the type shown below (grouping employees by first letter of surname).
#model IQueryable<IGrouping<string, Employee>>
I'm unsure why but it doesn't let me have a model of this type. The error message I get is:
An opening "<" is missing the corresponding closing ">". Which is incorrect.
I know I can create a view specific model and populate that instead but I'd like to know why this model doesn't seem to work?
By default, a very limited set of namespaces are available for direct use in razor views. Try to expand it to fully qualified names and see if the problem persists:
#model System.Linq.IQueryable<System.Linq.IGrouping<string, Name.Space.Employee>>
I don't know why you'd be getting this error, since you appear to be using correct Razor code. It's possible that there's actually a bug elsewhere in the page that is being made manifest through this incorrect error message.
A workaround, which may help you determine the real source of the bug, would be to create your own strongly-typed model class, which could have this data as its property:
public class EmployeeListViewModel
{
public IQueryable<IGrouping<string, Employee>> EmployeesByCompanyTitle {get;set;}
}
(There are those who would argue that this is a better approach anyway, since you can now add information to your view model more easily.)
Let's say I have the following selectlist (Countries) in a ViewModel:
//..
private static string[] _countries = new[] {
"USA",
"Canada",
"Japan"
};
//...
SelectList Countries = new SelectList(_countries, dinner.Country);
//...
And I render a dropdown list in the following fashion:
<%: Html.DropDownListFor(m => m.Dinner.Country, Model.Countries) %>
I noticed that using firebug, I can inject my own values into the DropDownList and that value may be inserted into the database.
What is the best way to validate that there are no injected values (preferably a DRY method)?
I would recommend taking advantage of DataAnnotations and create your own custom validation attribute.
This provides a way to encapsulate your validation logic (satisfying your DRY requirement), and will be applied server-side (preventing html manipulations like the one you described).
You should always validate your data server side anyways before inserting in the DB. If you had a key constraint it wouldn't be such an issue because the update or insert would fail. In this case though you should have a server side business rule to validate your object before doing the SQL call.
Since your building a list from a static list of items, the list should be available to your business layer so that you can compare against it to make sure that the value contained in your model is valid. You can add a method to your object such as IsValid or something that would do a quick validation and check that the values do exist for these hard coded selections.