How to get raw validationmessages in ValidationMessageFor? - asp.net-mvc

I got an asp.net mvc4 application where I'd like to retrieve only the exact error string of validationmessagefor and not the span tag, classes etc.
Currently the following is being returned from ValidationMessageFor:
<span class="field-validation-error" data-valmsg-replace="true" data-valmsg-for="Password">
<span class="" for="Password" generated="true">The Password field is required.</span>
</span>
I would like to only have returned: The Password field is required.
It's because I need to insert the exact value into another element attribute (so I can't use the HTML tags).
Essentially I need a custom ValidationMessageFor html helper that only returns the error message itself.
How would I do this?

Any error messages are a part of your ModelState.
For example, if you have a required field FirstName you can get the error message like this:
ModelState["FirstName"].Errors[0].ErrorMessage
In this case, you expect that there is only one error. This property returns 'The FirstName field is required'.
You can access ModelState from your controller or from your view by using ViewDate.ModelState.
ModelState is of type ModelStateDictionary which is an IDictionary<string, ModelState>. It only has an indexer property of type string. Because of this you can't access it by using ModelState[p => p.Property]. However, you can easily create an extension method that helps you with this:
public static class ModelStateExtension
{
public static ModelState For<TModel>(this ModelStateDictionary dictionary,
Expression<Func<TModel, object>> expression)
{
string propertyName = ExpressionHelper.GetExpressionText(expression);
return dictionary[propertyName];
}
}
You can use it like this:
string errorMessage = ModelState.For<Person>(p => p.FirstName).Errors[0].ErrorMessage;
If you want, you can let the extension method return the ErrorMessage for the first error directly instead of the ModelState.

To access errors you should use ModelState.Errors property. It contains
ModelErrorCollection
whith is a collection of
ModelError
Refer here http://msdn.microsoft.com/en-us/library/system.web.mvc.modelstate.errors%28v=vs.108%29.aspx
You can create your own helper then, and access those messagess on client-side.

For a specific property you can use
ViewData.ModelState["Password"].Errors

Related

ModelState binding custom array of checkboxes

ViewModel Binding is working, the object passed back to the edit controller contains the correct values, which is a list of selected options. However, ModelState binding is not working, the model state AttemptedValues exist, but aren't being reloaded into the fields.
I have a model with the following properties
class Model
{
public List<string> AvailableValues { get; set; }
public List<string> SelectedValues { get; set; }
}
But in my view I have some categorization, so I can't do a direct foreach.
foreach (var category in CatgoryList.Categories)
{
foreach (var available in Model.AvailableValues.Where(x => category.AvailableValues.Contains(x))
{
var check = Model.SelectedValues!= null && Model.SelectedValues.Contains(available.Id);
check &= (ViewData.ModelState["SelectedValues"] != null) && ViewData.ModelState["SelectedValues"].Value.AttemptedValue.Contains(available.Id);
<input type="checkbox" name="SelectedValues" id="available.Id" value="available.Id" checked="#check"/>#available.FriendlyName<br>
}
}
The ModelState does contain SelectedValues from the previous post, but it doesn't auto-bind, because I have a custom field for the checkboxes.
This code is smelly
Is there a better way to get the data to load from the Attempted Value
EDIT:
Ok, so my question wasn't clear enough, let me clarify.
On a validate, I'm retuning the same view if there was an error.
The modelstate is holding the previously entered values in ModelState["field"].Value.AttemptedValue.
With fields created using the helpers, TextboxFor, CheckboxFor, etc, these values are automatically filled in.
However, when using the normal reflexes for checkbox binding, only the values of the checked checkboxes are returned in the data object passed back to the controller. This means I'm not using the logic that fills values in from the ModelState.
What I've done is dig through the modelstate myself for the attempted values, because they do exist under the field name "SelectedValues". But I have to manually apply them. The value there looks like this.
ModelState["SelectedValues"] = "Value1;Value2;Value4"
Is there a better way to get the data to load from the Attempted Value in the model state.
The primary "smell" (to use your term) I see here is that the code you have in the nested foreach is written directly in your view (*.cshtml), but code of that complexity should be in your Controller action.
You should calculate and generate all the data your view will need in the controller, and then pass that data through to the view using Model (looks like you are already doing that) and you can also use the ViewBag to pass additional data not contained in your Model. Then the view is just responsible to generate the HTML.
That's the other problem I see with your code - you are referencing the ViewData.ModelState which is highly unusual to see in a view. The ModelState should be examined in the controller before you even decide which view to render.
It looks like maybe you are just passing data through ViewData.ModelState that should actually be passed through ViewData/ViewBag.
You can read more about passing data to a view here.
Ok, so basically, I couldn't find anything that will do this for me. The default Html helper methods just don't cover this scenario.
So, I wrote an extension method.
Basically it pulls in the enumerator from the model using the expression you send to it, just like any other helper, but you also send the entry in the list you want to build a checkbox against.
It ends up looking like this.
#Html.CheckboxListEntryFor(x => x.SelectedEntries, AvailableEntries[i].Id)
The method does the following
Get the propertyInfo for the list and check if selected entries contains the values.
Check if the ModelState is invalid, if so, overwrite the checked value with the modelstate entry
build an html checkbox that uses the property name as the name and id of the checkbox, and sets checked based on the previous steps.
public static MvcHtmlString CheckboxListEntryFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression, string entryValue)
{
PropertyInfo info = GetPropertyInfo(typeof (TModel), expression);
var enumerator = info.GetValue(htmlHelper.ViewData.Model);
var check = enumerator != null && ((IList) enumerator).Contains(entryValue);
if (!htmlHelper.ViewData.ModelState.IsValid)
{
check = htmlHelper.ViewData.ModelState[info.Name] != null &&
htmlHelper.ViewData.ModelState[info.Name].Value.AttemptedValue.Contains(entryValue);
}
var fieldString = String.Format(
"<input type=\"checkbox\" name=\"{0}\" id =\"{1}\" value=\"{1}\"{2}/>",
info.Name, entryValue, check ? " checked=\"checked\"" : string.Empty);
return MvcHtmlString.Create(fieldString);
}

Using TextBoxFor to set HTML attributes that have a namespace prefix

I am converting some existing HTML to work with ASP.NET MVC, and have some forms containing input fields that have additional attributes (that, for now, I need to preserve) that contain a namespace prefix:
<input type="text" foo:required="true" foo:message="Please enter a username" />
I would like to use the TextBoxFor helper method overload that allows htmlAttributes to be specified using collection initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new { foo:required="true", foo:message="Please enter a username" })
However, this syntax is not valid, due to the colon in foo:required (etc).
Instead, I am having to use this more verbose dictionary initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new Dictionary<string, object>
{
{ "foo:required", "true" },
{ "foo:message", "Please enter a username" }
})
Is it possible to use a variation on the first syntax, somehow escaping (for want of a better word) the colons?
Or
Is this a scenario where the TextBoxFor helper method does not really help, and would it be simpler to just keep the existing raw HTML, and add value="#Model.UserName" to the input element?
The first syntax is using an anonymous object, for which the same rules regarding how to create identifiers in C# apply:
You can use any unicode character of the classes Lu, Ll, Lt, Lm, Lo, or Nl
You can scape C# keywords using "#" as in new { #class = "foo" };
Since the colon belongs to the Po unicode class it cannot be used in an identifier. (In C# you can use the static method char.GetUnicodeCategory in order to check the class of any character)
Additionally, and only in MVC, when using an anonymous object for the html attributes in the helper, any attribute name with underscores will be replaced by hyphens. This is due to the method HtmlHelper.AnonymousObjectToHtmlAttributes
Back to your case and regarding your options, if those are not too widely used (like in a couple of views) I would consider staying with the Dictionary syntax of the TextBoxFor helper. You will still get the automatic generation of the id\name properties in sync with the model binding, and you will get any other attributes from the model metadata like the unobtrusive validation attributes. (although looking at the attributes you want to preserve, it seems you won´t need the unobtrusive validation ones :) )
However if the id\name will be as simple as the name of the property and you don´t need any other html attributes that would be generated automatically using the helper, then going for the raw HTML makes sense. (as the dictionary syntax is quite ugly)
In case you use it widely across the application, then in my opinion the most sensible approach may be creating your own helper, like #Html.LegacyTextBoxFor(...). That helper will render those legacy attributes you want to kepp, and you can incorporate additionaly logic similar to the standard helpers for id\name attribute creation.
Something like this:
public class FooAttributes
{
public bool Required { get; set; }
public string Message { get; set; }
}
public static class FooHelper
{
public static MvcHtmlString LegacyTextboxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, FooAttributes fooAttributes)
{
var fieldName = ExpressionHelper.GetExpressionText(expression);
var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var value = metadata.Model;
TagBuilder tag = new TagBuilder("input");
tag.Attributes.Add("name", fullBindingName);
tag.Attributes.Add("id", fieldId);
tag.Attributes.Add("type", "text");
tag.Attributes.Add("value", value == null ? "" : value.ToString());
if (fooAttributes != null)
{
if (fooAttributes.Required) tag.Attributes.Add("foo:required", "true");
if (!String.IsNullOrEmpty(fooAttributes.Message)) tag.Attributes.Add("foo:message", fooAttributes.Message);
}
return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}
}
That can be used as:
#Html.LegacyTextboxFor(model => model.UserName, new FooAttributes {Required=true, Message="Please enter a value." })
And will generate this html:
<input foo:message="Please enter a value." foo:required="true" id="UserName" name="UserName" type="text" value="">
And once you have your own helper, you could add additionaly logic, for example logic that will generate those attributes from the model metadata and its data annotation attributes...
I have extended my answer more than intended, but I hope it helps!

MVC - Manually set form submit value from ViewData

In a web app I'm creating, I have a list of clients. And each client has a sub-list of projects.
This is my Client model:
Public Class ppClient
<Key()>
Public Property ClientID As Integer
Public Property Name As String
Public Overridable Property Projects As ICollection(Of ppProject)
End Class
And here is my Project model:
Public Class ppProject
<Key()>
Public Property ProjectID As Integer
Public Property ClientID As Integer
Public Property ProjectTitle As String
Public Overridable Property Client As ppClient
End Class
The problem I am encountering is that I am trying to manually add a form value when a new project is being created. I have the ClientID saved in ViewData, and so I don't want to ask the user for it. This is my view and form submission:
ModelType MVCnewDB.ppProject
#Using Html.BeginForm()
#Html.ValidationSummary(True)
#* Needs to submit ClientID *#
Html.EditorFor(Function(model) model.ProjectTitle)
Html.ValidationMessageFor(Function(model) model.ProjectTitle)
#<input type="submit" value="Create Project" />
End Using
<div>
#Html.ActionLink("Back", "Index")
</div>
So how do I get the form to submit ClientID as my ViewData value? Can I make a call to Html.HiddenFor and stuff a value into it or something?
Any help would be appreciated.
Ah okay - I've found what I was looking for. I was looking for #Html.Hidden("ClientID", ViewData("key")) which submits the given ViewData value to the form as the ClientID field.
You just need to add a hidden field. I don't know how you would do it in VB, but in C# it would be like this:
#Html.HiddenFor(x => ViewBag.ClientID)
or
#Html.HiddenFor(x => ViewData["ClientID"])
This is how I interpret your question:
You are on a page with a model bound to your ppProject class
You have a ClientID somewhere and you want to know where to put it on the form so that...
You want the form submission to include the ClientID so you can relate the ppProject to a client.
Is this correct?
If so, you have a couple options. If you've included your ClientID in a ViewBag, you can use a hidden field to store the value and submit it. On your action, you can actually specify more than one parameter. The first can be the ppProject class you want the model binder to bind your fields to, and another string parameter with the same name as the hidden field containing your ClientID value. As long as the parameter matches the hidden field name exactly, the model binder will match the two up and you'll get the ClientID.
(In C# because my VB.NET skills are too rusty for it to make sense:)
public ActionResult NewProject(ppProject projectInfo, string ClientID)
{
...
}
The above assumes you keep your view as-is but you add something like this:
<input type="hidden" name="ClientID" value="#ViewBag.ClientID" />
Or possibly (just a guess!):
Html.HiddenFor(Function(model) ViewBag.ClientID)
This assumes you've matched the ViewBag property name to the parameter name on your ActionResult in your controller. (Just replace the ViewBag with whatever you're using to store the ClientID value.)

asp.net mvc 3 validation on data type

I am trying to realize valition on data type. I have used DataAnnotations, but for data type it's not showing customized message
for example when I' am trying enter string data into int typed field. How I can customize messages in this case?
If I had to guess, you sound like you want a custom message to display when validating one or more fields in your model. You can subclass the DataAnnotations.ValidationAttribute class and override the IsValid(object) method and finally setting a custom ErrorMessage value (where ErrorMessage already belongs to the ValidationAttribute class)
public class SuperDuperValidator : ValidationAttribute
{
public override bool IsValid(object value)
{
bool valid = false;
// do your validation logic here
return valid;
}
}
Finally, decorate your model property with the attribute
public class MyClass
{
[SuperDuperValidator(ErrorMessage="Something is wrong with MyInt")]
public int MyInt { get; set; }
}
If you're using out-of-the-box MVC3, this should be all you need to propertly validate a model (though your model will probably differ/have more properties, etc) So, in your [HttpPost] controller action, MVC will automagically bind MyClass and you will be able to use ModelState.IsValid to determine whether or not the posted data is, in fact, valid.
Pavel,
The DataAnnotations DataType attribute does not affect validation. It's used to decide how your input is rendered. In such a case, David's solution above works.
However, if you want to use only the built-in validation attributes, you probably need to use the Range attribute like this:
[Range(0, 10, ErrorMessage="Please enter a number between 0 and 10")]
public int MyInt { get ; set ;}
(Of course, you should really be using the ErrorMessageResourceName/Type parameters and extract out hard-coded error message strings into resx files.)
Make sure to let MVC know where to render your error message:
<%= Html.ValidationMessageFor(m => m.MyInt) %>
Or you can just use EditorForModel and it will set it up correctly.
I don't think this has been answered because I have the same issue.
If you have a Model with a property of type int and the user types in a string of "asd" then the MVC3 framework binding/validation steps in and results in your view displaying "The value 'asd' is not valid for <model property name or DisplayName here>".
To me the poster is asking can this message that the MVC3 framework is outputting be customized?
I'd like to know too. Whilst the message is not too bad if you label your field something that easily indicates an number is expected you might still want to include additional reasons so it says something like:
"The value 'asd' is not valid for <fieldname>; must be a positive whole number."
So that the user is not entering value after value and getting different error messages each time.

MVC2: Can't convert String to ExtensionDataObject (without knowing I wanted to)

I'm getting the following InvalidOperationException:
The parameter conversion from type 'System.String' to type 'System.Runtime.Serialization.ExtensionDataObject' failed because no type converter can convert between these types.
In a Post action on my ASP.Net MVC2 page, but I'm really not sure what it's referring to. I'm using data annotation validation:
public class FamilyPersonMetadata
{
[Required(ErrorMessage = "Name Required")]
public String Name;
[Required(ErrorMessage = "Date of Birth required")]
[DateTime(ErrorMessage = "Invalid Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d")]
public DateTime DateOfBirth;
}
[MetadataType(typeof(FamilyPersonMetadata))]
public partial class FamilyPerson
{
}
And my view inhertis from a ViewPage with a subtype of FamilyPerson. I just create controls with names matching those of FamilyPerson and then submit the form, but for some reason my ModelState is invalid and the above error is apparently the reason. I'm quite perplexed as to the nature of the error. Similar code is working for other views and actions.
Could someone point me in the direction of things to look at that might cause this?
Regarding [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d")]
"{0:d" should be "{0:d}"
Here's an actual explanation of the problem, and how to solve it: http://www.shawson.co.uk/codeblog/mvc-strongly-typed-view-returns-a-null-model-on-post-back/comment-page-1/
Short summary: Don't give your view parameters the same names as model fields, unless they represent the same value (and have the same type).
It seems to have gone away on its own. Weird.
This may help someone:
I had this exception being thrown because i had mulitiple forms in my view.
However one of the forms did not explicitly set the 'action' attribute. That is, i was using this constructor:
#using (Html.BeginForm())
Instead of this one:
#using (Html.BeginForm("ACTION_METHOD", "CONTROLLER", FormMethod.Post, null))
This would result in the incorrect parameters being posted with the Form. Spoecifically, the business model object was being included on the Form when it shouldn't be. In turn, .Net was then trying to convert a System.String to a business model object when it shouldn't be attempting such a conversion.
The solution is to use the later overloaded method and ensure that the correct 'action' attribute is being set for your Form upon postback.
FYI: To inspect the 'action' attribute of your Form, use FireBug and inspect the HTML, find the Form element and the 'action' attribute will be there with all the parameters that will be posted back to the serber when that Form is submitted.

Resources