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; }
}
}
@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