I am working with a custom validation attribute and want to show my validation message, where I will specify a name like this:
return new ValidationResult(FormatErrorMessage("Hobbies"));
I found this html was there on the page:
<span data-valmsg-replace="true" data-valmsg-for="Hobbies" class="field-validation-valid text-danger"></span>
When I return message from the IsValid function of the ValidationAttribute class like this:
return new ValidationResult(FormatErrorMessage("Hobbies"));
Where I specify the name "Hobbies" then it should be added into
<span data-valmsg-for="Hobbies">
Why was my error message was not added?
If you want me to paste full code then please tell me.
I apologize, I'm not completely sure about your question. However, since you said "give me a clue what kind of mistake I have done," I'm going to go with explaining the approach that I've taken in a project which utilizes the ViewModel and a couple of custom validation-handling methods.
I don't think this is a direct answer to your question, but it should give you enough information to be able to make custom methods that build the HTML with the error message content inside it the way you want if you are using MVC w/ razor.
Utilizing ViewModels in razor for validation
Normally, with .NET MVC razor (you didn't mention which version of MVC you were using), you just utilize attributes in the view model for validation, e.g.
public class MyViewModel {
[Required(ErrorMessage = "Required")]
public string Name {get; set;}
}
These automatically get validated on post. This will set the property of the view model "Name" to required, and when the validation fails, the error message will be "Required."
Now, if you are doing some processing server side and doing complex validation that is not easy to setup in attributes, then you can manually add an error to your model, like so (my code for captcha validation):
//inside controller HttpPost method
var isCaptchaValid = ReCaptcha.IsCaptchaValid(Request.Form["g-recaptcha-response"] ?? "");
if (!isCaptchaValid)
{
ModelState.AddModelError("Captcha", "Verification Required");
}
Where "Captcha" is the field name and "Verification Required" is the error message.
Displaying Custom Error Messages
Then, stepping away from the standard approach, I have some classes that will build the Bootstrap 3 class "has-error" for the form-group and a custom error that wraps the error message in a label:
public static class HtmlExtensions {
public static string GetErrorClass(this HtmlHelper htmlHelper, bool hasError)
{
return " has-error";
}
public static MvcHtmlString ErrorLabel<TModel>(this HtmlHelper<TModel> helper, ModelStateDictionary modelState, string fieldName)
{
if (modelState[fieldName] != null )
{
var error = modelState[fieldName].Errors.FirstOrDefault();
if (error != null)
{
var span = new TagBuilder("span");
span.AddCssClass("label label-danger");
span.InnerHtml = error.ErrorMessage;
return MvcHtmlString.Create(span.ToString(TagRenderMode.Normal));
}
}
return MvcHtmlString.Empty;
}
}
From there, you can use it just like any of the other #Html
<div class='form-group#Html.GetErrorClass("Name")'>
<label class='control-label col-xs-12 col-sm-6' for='name'>#Html.ErrorLabel(ViewData.ModelState,"Name") Name:</label>
<div class='col-xs-12 col-sm-6'>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
</div>
</div>
Anyway, I hope that helps you with what you are trying to do. If this is so far away from an answer to your problem, please let me know and I'll remove the answer.
Applying to your HTML requirements:
Oh, and I guess to build the HTML the way you are wanting to, doing it this way, the extension method would look like this:
public static MvcHtmlString MyErrorExtension<TModel>(this HtmlHelper<TModel> helper, ModelStateDictionary modelState, string fieldName)
{
if (modelState[fieldName] != null)
{
var error = modelState[fieldName].Errors.FirstOrDefault();
//<span data-valmsg-replace="true" data-valmsg-for="Hobbies" class="field-validation-valid text-danger"></span>
var span = new TagBuilder("span");
span.Attributes.Add("data-valmsg-replace", "true");
span.Attributes.Add("data-valmsg-for", fieldName);
span.AddCssClass("field-validation-valid text-danger");
if (error != null)
{
span.InnerHtml = error.ErrorMessage;
}
return MvcHtmlString.Create(span.ToString(TagRenderMode.Normal));
}
return MvcHtmlString.Empty;
}
The above method will create your tag regardless, and it will populate it with the first error it finds if there are any errors for that field name.
Related
This is code from the Contoso University online example:
Controller:
[HttpGet]
public ActionResult Edit(int id)
{
Department department = departmentService.GetById(id);
PopulateAdministratorDropDownList(department.PersonID);
return View(department);
}
// POST: /Department/Edit/5
[HttpPost]
public ActionResult Edit(Department department)
{
try
{
if (ModelState.IsValid)
{
departmentService.Update(department);
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem
persists, see your system administrator.");
}
PopulateAdministratorDropDownList(department.PersonID);
return View(department);
}
private void PopulateAdministratorDropDownList(object selectedAdministrator = null)
{
var administrators = instructorService.GetAll().OrderBy(i => i.LastName);
ViewBag.PersonID = new SelectList(administrators, "PersonID", "FullName",
selectedAdministrator);
}
View:
<div class="editor-field">
#Html.DropDownList("PersonID", String.Empty)
#Html.ValidationMessageFor(model => model.PersonID)
</div>
My question is: if in the View we are not accessing the ViewBag.PersonID (we just create a DropDownList, that generates an html select list with the ID="PersonID", without any default selected value), how the hell the ViewBag.PersonID property is binded to that DropDownList? What's happening behind the scenes? This looks like magic!!!
The second question is when Posting the data, I think the controller searches in the view for any html form field whose ID matches a property in the model and that's how we get the selected Department.PersonID on the postback, even if the view code doesn't reference the model (somtheing like model => model.PersonID) right?
Behind the scenes:
The view is calling Html.DropdownList(this HtmlHelper htmlHelper, string name, string optionLabel) which eventually ends up calling SelectExtensions.SelectInternal(htmlHelper, metadata, optionLabel, expression, selectList, allowMultiple, htmlAttributes1) This checks selectList1 for null, and if if it is, it calls SelectExtensions.GetSelectData(htmlHelper, name) which does the magic part of checking the View data for a key matching the name you passed in.
Posting:
Your assumptions here are pretty much right, but the in addition to form fields the framework will also check the query string and route data and any other IValueProvider that is plugged in to the pipe line.
I have some Customer Details and I only want to show fields which have a value.
For example if Telephone is null don't show it.
I currently have in my view model
public string FormattedTelephone
{
get { return string.IsNullOrEmpty(this.Telephone) ? " " : this.Telephone; }
}
And in my view
#Html.DisplayFor(model => model.FormattedTelephone)
This is working correctly, however, I would like to show the Field Name if the field has a value e.g.
Telephone: 02890777654
If I use #Html.DisplayNameFor in my view it shows the field name even if the field is null.
I also want to style the field name in bold and unsure of where I style it - the view or the view model.
For the bold style you can use this bit of code in your view, but of course it's proper to use an external style sheet.
<style type="text/css">
.telephone{
font-weight: bold;
}
</style>
You can do the check for null in your view and conditionally display the data:
#if (Model.FomattedTelephone != null)
{
<div class="telephone">
#Html.DisplayFor(model => model.FormattedTelephone)</div>
}
For style add a class for to the span you can put around field name.
You could create your own HtmlHelper that will only write if string is not null or empty.
Or you could add a DisplayTemplates something like here:
How do I create a MVC Razor template for DisplayFor()
For more background on helpers in razor read the following
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
And if they're in your App_Code folder read the answer to this
Using MVC HtmlHelper extensions from Razor declarative views
You'll probably want to over the default helper page with this (and inherit in your helper classes in App_Code)
public class WorkaroundHelperPage : HelperPage
{
// Workaround - exposes the MVC HtmlHelper instead of the normal helper
public static new HtmlHelper Html
{
get { return ((WebViewPage)WebPageContext.Current.Page).Html; }
}
public static UrlHelper Url
{
get { return ((WebViewPage) WebPageContext.Current.Page).Url; }
}
}
I would make a helper for this, something like this:
using System.Web.Mvc.Html;
public static class HtmlHelpers
{
public static MvcHtmlString LabelDisplayFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
StringBuilder html = new StringBuilder();
string disp = helper.DisplayFor(expression).ToString();
if (!string.IsNullOrWhiteSpace(disp))
{
html.AppendLine(helper.DisplayNameFor(expression).ToString());
html.AppendLine(disp);
}
return MvcHtmlString.Create(html.ToString());
}
}
Now, when you are in your View, you can simply do this (given you include the namespace in your view or web.config):
#Html.LabelDisplayFor(model => model.FormattedTelephone)
All it really does is check to see if your display helper is not an empty string, if it is, it will simply append your LabelFor and DisplayFor, if not, it will return an empty string.
I usually prefer to use Display/Editor Templates instead of HtmlHelper. Here is template that I have used to perform exactly the same task, its designed for bootstrap data list but anyone can adjust it easily.
#if (Model == null)
{
#ViewData.ModelMetadata.NullDisplayText
}
else if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
}
else
{
<dl class="dl-horizontal">
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if(MvcHtmlString.IsNullOrEmpty(Html.Display(prop.PropertyName)))
{
continue;
}
if (prop.HideSurroundingHtml)
{
#Html.Display(prop.PropertyName)
}
else
{
<dt>#prop.GetDisplayName()</dt>
<dd>#Html.Display(prop.PropertyName)</dd>
}
}
</dl>
}
Key line is:
if(MvcHtmlString.IsNullOrEmpty(Html.Display(prop.PropertyName)))
Its based on object template so to use it you need use it on object or whole model like
#Html.DisplayForModel("TemplateName")
Is it possible to customize the Html.ValidationMessageFor method so that it produces different HTML?
I want to do something similar to:
<div class="field-error-box">
<div class="top"></div>
<div class="mid"><p>This field is required.</p></div>
</div>
I am not sure if it's possible to use paragraph instead of default span, as it may make impossible for validation plugin to place error messages. But for div -s, thats easy - you could write custom html helper.
Something along these lines (may need further testing/coding). You will need to include the namespace of this static extension method in your view, or put this into System.Web.Mvc.Html directly.
public static class Validator
{
public static MvcHtmlString MyValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
TagBuilder containerDivBuilder = new TagBuilder("div");
containerDivBuilder.AddCssClass("field-error-box");
TagBuilder topDivBuilder = new TagBuilder("div");
topDivBuilder.AddCssClass("top");
TagBuilder midDivBuilder = new TagBuilder("div");
midDivBuilder.AddCssClass("mid");
midDivBuilder.InnerHtml = helper.ValidationMessageFor(expression).ToString();
containerDivBuilder.InnerHtml += topDivBuilder.ToString(TagRenderMode.Normal);
containerDivBuilder.InnerHtml += midDivBuilder.ToString(TagRenderMode.Normal);
return MvcHtmlString.Create(containerDivBuilder.ToString(TagRenderMode.Normal));
}
}
As you see, this uses default ValidationMessageFor method, to not interfere with validation-plugin error message processing.
And you use this simply, as default validation message helper
#Html.MyValidationMessageFor(model => model.SomeRequiredField)
I used another way:
public static MvcHtmlString DivValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(htmlHelper.ValidationMessageFor(expression).ToString().Replace("span", "div"));
}
This way you can use the built in way, but replace the span with a div.
If you need any other overloads of the function, just duplicate as necessary.
You can implement your own ValidationMessageFor helper to emit your desired output or use some javascript to add/modify the rendered HTML code but the custom ValidationMessageFor implementation is the cleaner approach IMHO.
To implement your own ValidationMessageFor helper take a look at the ValidationExtensions.ValidationMessageFor and ValidationMessageHelper methods in the ASP.NET MVC source code.
Implementation Hints
Since GetFormContextForClientValidation is internal you have to work around that implementation by duplicating the internal functionality in your code:
FormContext formContext = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;
Some other methods are private in ValidationExtensions like GetUserErrorMessageOrDefault you would need to duplicate that code too. What you can do to avoid duplicating code is to let ValidationExtentensions.ValidationMessageFor render the validation message string that is wrapped in a span and afterwards change the rendered string according to your requirements. Keep in mind that "null" is returned in case no error was found and that you'll need the data- HTML attributes in case you have unobtrusive JavaScript enabled.
You can download the ASP.NET MVC 3 source code from here
The only need for change of the default tag generation was in my case, that spans behavior results in anoying margin setups.
I resolved this by using 'display: block'
Maybe this helps some people..
Maybe you can put that code
string propertyName = ExpressionHelper.GetExpressionText(expression);
string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));
if (helper.ViewData.ModelState[name] == null ||
helper.ViewData.ModelState[name].Errors == null ||
helper.ViewData.ModelState[name].Errors.Count == 0)
{
return MvcHtmlString.Empty;
}
on top of the answered function, so that the div doesn't appear on the form load.
I created ValidationMessageAsStringFor which just returns the error message as string. It is basically a simplified version of ValidationMessageFor:
public static MvcHtmlString ValidationMessageAsStringFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
var field = ExpressionHelper.GetExpressionText(expression);
string modelName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(field);
if (!helper.ViewData.ModelState.ContainsKey(modelName))
{
return null;
}
var modelState = helper.ViewData.ModelState[modelName];
var modelErrors = (modelState == null) ? null : modelState.Errors;
var modelError = ((modelErrors == null) || (modelErrors.Count == 0)) ? null : modelErrors.FirstOrDefault(m => !String.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0];
if (modelError == null)
{
return null;
}
var errorMessage = GetUserErrorMessageOrDefault(helper.ViewContext.HttpContext, modelError, modelState);
return MvcHtmlString.Create(errorMessage);
}
private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error, ModelState modelState)
{
if (!string.IsNullOrEmpty(error.ErrorMessage))
{
return error.ErrorMessage;
}
if (modelState == null)
{
return null;
}
return modelState.Value?.AttemptedValue;
}
With this in place and after importing the namespace containing the new helper, just create the HTML code you need:
<div class="field-error-box">
<div class="top"></div>
<div class="mid"><p>#Html.ValidationMessageAsStringFor(m => m.FieldName)</p></div>
</div>
Yes, just use a metamodel for the field:
[MetadataType(typeof(YourMetaData))]
public partial class YOURCLASS
{
[Bind(Exclude = "objID")]
public class YourMetaData
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Please enter a name")]
public object Name { get; set; }
}
}
Change your message at the ErrorMessage field :)
Hope this help :)
If two of textboxes fail validation at once then the ValidationSummary displays the same message twice.
Am I doing something wrong? Or is there a setting I can change to hide duplicate messages?
I have broken it down to the simplest example:
View:
#model MyModel
#Html.ValidationSummary()
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
Model:
public class MyModel : IValidatableObject
{
public int A { get; set; }
public int B { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Some logic goes here.
yield return new ValidationResult("Validation failed", new[] { "A", "B" });
}
}
Result:
They are not duplicate from the point of view of ValidationSummary - you are assigning model state error to both fields A and B, so there must be 2 errors in validation summary. It doesnt "know" that they are the same.
Easy solutions :
assign model only to one of them
exclude property-assigned errors from summary - Html.ValidationSummary(true)
A little bit harder solution :
make your own ValidationSummary helper, call standard validation summary logic in it, and then filter the result in "select distinct" way (linq is your friend here).
EDIT:
something like this for example :
public static class ValidationExtensions
{
public static MvcHtmlString FilteredValidationSummary(this HtmlHelper html)
{
// do some filtering on html.ViewData.ModelState
return System.Web.Mvc.Html.ValidationExtensions.ValidationSummary(html);
}
}
Whack this is your View
<ul class="validation-summary-errors">
#{
string errorMessage = "", previousError = "";
foreach (ModelState modelState in (ViewContext.ViewData.ModelState.Values)){
foreach (ModelError modelError in modelState.Errors)
{
errorMessage = modelError.ErrorMessage;
if (errorMessage != previousError)
{
<li>#modelError.ErrorMessage</li>
previousError = modelError.ErrorMessage;
}
}
}
}
</ul>
You might be able to improve this as it only works when 2 consecutive errors are the same, if it jumbles the order this might not work, but this will start you off. I suppose you could build an array of error messages and check the error off it each run through, but this solution seems to work most of the time.
ValidationSummary method returns property-level and model-level errors. It just enumerates all validation messages if you don't specify any arguments.
You can:
1) Use different message for field A and B
// logic here
yield return new ValidationResult("Validation failed for left field", new[] { "A" });
// logic here
yield return new ValidationResult("Validation failed for right field", new[] { "B" });
or, in your view
2) Call ValidationSummary with excludePropertyErrors argument set to true - ValidationSummary(true). And place call Html.ValidationMessage[For] near each of your fields.
UPDT:
... and third case:
In your model add common message (model-level):
//logic here
yield return new ValidationResult("Validation failed");
yield return new ValidationResult("any text or empty string", new[] { "A", "B" });
In your view exclude property messages but don't add ValidationMessage for fields:
#model MyModel
#Html.ValidationSummary(true)
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
So you'll get single message and both red boxes.
Coming from the asp.net background, I really appreciated the concept of 'validationGroup' when adding validation to a page. I've been searching for a corresponding concept within mvc.net and haven't had much luck.
Is this concept available in mvc.net? If not, what alternatives do I have?
Unfortunately no, it doesn't come with anything like that.
I blogged about a workaround a wee while back.
ASP.NET MVC - Validation Summary with 2 Forms & 1 View
The jist of the blog post:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static string ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return string.Empty;
}
}
}
And
<h2>Register</h2>
<%= Html.ActionValidationSummary("Register") %>
<form method="post" id="register-form" action="<%= Html.AttributeEncode(Url.Action("Register")) %>">
... blah ...
</form>
<h2>User Login</h2>
<%= Html.ActionValidationSummary("LogIn") %>
<form method="post" id="login-form" action="<%= Html.AttributeEncode(Url.Action("LogIn")) %>">
... blah ...
</form>
HTHs,
Charles
Expanding on Charlino's answer, and including HtmlAttributes and other ValidationSummary properties:
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action, bool excludePropertyErrors, string message, object htmlAttributes = null)
{
var currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
{
return html.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
}
return new MvcHtmlString(string.Empty);
}
Charles's method was the only approach I could find that actually worked for my purposes!
(I.e. two forms on one MVC page -> without doing forms inside partials and ajax loads for the partials. This was no good for me, as I wanted to return differing result sets to be rendered outside the form div, depending on which form was submitted)
I would advise a slight modification to the Html Extension though, because you still want a validation summary to be rendered for the non-matched validation summary so that client side validation works:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return new MvcHtmlString("<div class=\"validation-summary-valid\" data-valmsg-summary=\"true\"><ul><li style=\"display:none\"></li></ul></div>");
}
}
}