Skip to content

Instantly share code, notes, and snippets.

@FWest98
Last active November 16, 2022 14:00
Show Gist options
  • Save FWest98/9141b3c2f260bee0e46058897d2017d2 to your computer and use it in GitHub Desktop.
Save FWest98/9141b3c2f260bee0e46058897d2017d2 to your computer and use it in GitHub Desktop.
.NET Core 3 Custom Validation Classnames
// This is the default partial generated in the template, if you have another place
// where you include the jQuery unobtrusive validation plugin, put this code there
@using Foo
@inject IOptions<ValidationOptions> validationOptionsHolder
@{ var validationOptions = validationOptionsHolder.Value; }
// Here is your normal environment stuff
<script>
(function () {
// Set default options for the validation
const defaultOptions = {
errorClass: "@validationOptions.InvalidInput", // or add custom, extra ones
validClass: "@validationOptions.ValidInput"
};
$.validator.setDefaults(defaultOptions);
$.validator.unobtrusive.options = defaultOptions;
})();
</script>
using System.Collections.Generic;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Antiforgery;
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.Extensions.Options;
namespace Foo
{
// Custom HTML Generator to override the default behaviour and replace the CSS classes as they emerge
public class CustomHtmlGenerator : DefaultHtmlGenerator
{
private readonly ValidationOptions _options;
public CustomHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder,
ValidationHtmlAttributeProvider validationAttributeProvider, IOptions<ValidationOptions> options) :
base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
{
_options = options.Value;
}
protected override TagBuilder GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression,
object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format,
IDictionary<string, object> htmlAttributes)
{
var tagBuilder = base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData, isChecked, setId, isExplicitValue, format, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
public override TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows,
int columns, object htmlAttributes)
{
var tagBuilder = base.GenerateTextArea(viewContext, modelExplorer, expression, rows, columns, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
public override TagBuilder GenerateValidationMessage(ViewContext viewContext, ModelExplorer modelExplorer, string expression,
string message, string tag, object htmlAttributes)
{
var tagBuilder = base.GenerateValidationMessage(viewContext, modelExplorer, expression, message, tag, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
public override TagBuilder GenerateValidationSummary(ViewContext viewContext, bool excludePropertyErrors, string message,
string headerTag, object htmlAttributes)
{
var tagBuilder = base.GenerateValidationSummary(viewContext, excludePropertyErrors, message, headerTag, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
private void FixValidationCssClassNames(TagBuilder tagBuilder)
{
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationInputCssClassName, _options.InvalidInput);
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationInputValidCssClassName, _options.ValidInput);
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationMessageCssClassName, _options.InvalidMessage);
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationMessageValidCssClassName, _options.ValidMessage);
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationSummaryCssClassName, _options.InvalidSummary);
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationSummaryValidCssClassName, _options.ValidSummary);
}
}
public static class TagBuilderHelpers
{
public static void ReplaceCssClass(this TagBuilder tagBuilder, string old, string val)
{
if (!tagBuilder.Attributes.TryGetValue("class", out string str)) return;
tagBuilder.Attributes["class"] = str.Replace(old, val);
}
}
}
using System;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.WsFederation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Foo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// All your usual stuff
// Configure Options
services.Configure<ValidationOptions>(Configuration.GetSection("Validation")); // or use a custom source
// Configure custom html generator to override css classnames
services.AddSingleton<IHtmlGenerator, CustomHtmlGenerator>();
}
}
}
namespace Foo
{
// Options class to set the validation classes. Of course, this is not required as you can
// also harcode the classnames in the CustomHtmlGenerator file.
public class ValidationOptions
{
public string ValidInput { get; set; }
public string InvalidInput { get; set; }
public string InvalidMessage { get; set; }
public string ValidMessage { get; set; }
public string InvalidSummary { get; set; }
public string ValidSummary { get; set; }
}
}
@lonix1
Copy link

lonix1 commented May 19, 2022

@FWest98 Thanks for this! Are you using this with v6, and if so, with bootstrap? I'm curious which classes to use, they change all the time. This code is great, it's a pity they didn't accept your PR.

@FWest98
Copy link
Author

FWest98 commented May 19, 2022

I am quite sure it still works on .NET 6, but I developed it for .NET Core 3.1 and then used it for .NET 5. I indeed use bootstrap (but an older version I believe) with the following part in my appsettings.json:

  "Validation": {
    "ValidInput": "is-valid",
    "InvalidInput": "is-invalid",
    "InvalidMessage": "invalid-feedback",
    "ValidMessage": "valid-feedback"
  }

@lonix1
Copy link

lonix1 commented May 19, 2022

Thank you!

@KennethScott
Copy link

Fantastic fix, thank you!

@b6025
Copy link

b6025 commented Nov 14, 2022

This is great, thanks! Is there any way we can control InvalidMessage / ValidMessage from the client side? I see that it ValidInput / InvalidInput works great (client and server), but InvalidMessage / ValidMessage seems to be server only and the client-side validator is still adding the 'field-validation-error' / 'field-validation-valid' classes. Thanks again!

@FWest98
Copy link
Author

FWest98 commented Nov 15, 2022

I think you'll have to dig into the inner working of the validator then... Not sure how that could be done.

@b6025
Copy link

b6025 commented Nov 16, 2022

I ultimately figured it out... the values are hard-coded in jquery.validate.unobtrusive.js so a direct change is required. I added a couple of getter methods to pull the class name from the options from '$jQval.unobtrusive.options', then replaced all the hard coded values with calls to the getter functions. Fingers crossed MS does something better in .NET 8!

Thanks again!

    function getFieldValidationErrorCss() {
        if ($jQval.unobtrusive && $jQval.unobtrusive.options) {
            if ($jQval.unobtrusive.options.fieldInvalidClass)
                return $jQval.unobtrusive.options.fieldInvalidClass;
        }
        
        return "field-validation-error";
    }

    function getFieldValidationValidCss() {
        if ($jQval.unobtrusive && $jQval.unobtrusive.options) {
            if ($jQval.unobtrusive.options.fieldValidClass)
                return $jQval.unobtrusive.options.fieldValidClass;
        }

        return "field-validation-valid";
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment