For the validation summary you usually have something like this:
<div asp-validation-summary="ModelOnly" class="..."></div>
which, in case there are errors with an empty string as field/attribute will be displayed inside that div within a <ul>
list.
What if I want to display them using a sequence of div
s with a specific class
attribute? Or any other custom formatting?
For field validation you usually do:
<div class="form-group">
<label asp-for="OldPassword"></label>
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
and the error gets inserted as text within the span
element.
I'm using a template that requires the has-errors
class to be applied to the form-group
div
element in case there are errors because it needs to style both the label and the input.
It also requires the span
to be a div
(for some unknown reasons) and surprisingly enough changing the span
to div
prevents the div from inserting the text of the error; not to mention that wrapping the span
inside a div
yields that problem that the div
has proper spacing applied to it so even if there are no errors the div shows an annoying space.
What is the most idiomatic way of handling custom form validation formatting (trying to DRY, since my application has many forms) with custom rules like the ones shown above?
Here are some extension points that you can consider to provide custom rendering for validation summary and field validation errors:
IHtmlGenerator
)Tag Helpers
)asp-validation-summary
and asp-validation-for
tag helpers use GenerateValidationSummary
and GenerateValidationMessage
methods of the registered implementation of IHtmlGenerator
service which is DefaultHtmlGenerator
by default.
You can provide your custom implementation deriving DefaultHtmlGenerator
and overriding those methods, then register the service at startup. This way those tag helpers will use your custom implementation.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<IHtmlGenerator, MyHtmlGenerator>();
}
Here is the link to source code of DefaultHtmlGenerator
to help you to customize the implementation.
Example - Creating a new implementation IHtmlGenerator
Here is just a simple example to show required namespaces and methods and simply what can goes into your custom implementation. After you provided custom implementation, don't forget to register it in ConfigureServices
like what I did above.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
namespace ValidationSampleWebApplication
{
public class MyHtmlGenerator : DefaultHtmlGenerator
{
public MyHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ValidationHtmlAttributeProvider validationAttributeProvider)
: base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
{
}
public override TagBuilder GenerateValidationMessage(ViewContext viewContext, ModelExplorer modelExplorer, string expression, string message, string tag, object htmlAttributes)
{
return base.GenerateValidationMessage(viewContext, modelExplorer, expression, message, tag, htmlAttributes);
}
public override TagBuilder GenerateValidationSummary(ViewContext viewContext, bool excludePropertyErrors, string message, string headerTag, object htmlAttributes)
{
return base.GenerateValidationSummary(viewContext, excludePropertyErrors, message, headerTag, htmlAttributes);
}
}
}
You also can author your custom tag helpers. To do so, it's enough to derive from TagHelper
and override Process
methods.
Then you can simply register created tag helpers in the view or globally in _ViewImports.cshtml
:
@using ValidationSampleWebApplication
@using ValidationSampleWebApplication.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, ValidationSampleWebApplication
Also when creating the custom tag helpers for validation you can consider:
Example - Adding hasError class to a form-group div
In this example, I've created a asp-myvalidation-for
which can be applied on div
elements this way <div class="form-group" asp-myvalidation-for="LastName">
and will add hasError
class to div
if the specified field has validation error. Don't forget to register it in _ViewImports.cshtml
like what I did above.
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace ValidationSampleWebApplication
{
[HtmlTargetElement("div", Attributes = MyValidationForAttributeName)]
public class MyValidationTagHelper : TagHelper
{
private const string MyValidationForAttributeName = "asp-myvalidation-for";
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
[HtmlAttributeName(MyValidationForAttributeName)]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
ModelStateEntry entry;
ViewContext.ViewData.ModelState.TryGetValue(For.Name, out entry);
if (entry != null && entry.Errors.Count > 0)
{
var builder = new TagBuilder("div");
builder.AddCssClass("hasError");
output.MergeAttributes(builder);
}
}
}
}
Example - Adding field-validation-error
class to a form-group div
In the following example, I've added div
support to the standard asp-validation-for
tag helper. The existing tag helper just supports div element. Here I've added div
support to the asp-validation-for
tag helper and in case of error, it will add field-validation-error
otherwise, in valid cases the div
will have field-validation-valid
class.
The default behavior of the tag is in a way that it doesn't make any change in content of the tag if the tag has contents. So for adding the tag helper to an empty span
will add validation error to span, but for a div having some contents, it just changes the class of div. Don't forget to register it in _ViewImports.cshtml
like what I did above.
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.TagHelpers;
namespace ValidationSampleWebApplication
{
[HtmlTargetElement("div", Attributes = ValidationForAttributeName)]
public class MytValidationMessageTagHelper : ValidationMessageTagHelper
{
private const string ValidationForAttributeName = "asp-validation-for";
public MytValidationMessageTagHelper(IHtmlGenerator generator) : base(generator)
{
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With