Search code examples
model-view-controllerruntime.net-6.0model-validation

Runtime Model Validation when Posting in .net core frontend


My current requirements are overriding validation at runtime when validating a models state after posting a form.

I have a working solution of using normal Data Annotations for model state validation, that's standard in the world of .net, but what I would like to do is, override that validation at runtime.

I know is possible using Microsoft.Practices.EnterpriseLibrary, here is an example of an stack exchange question on the topic, Runtime Model Validation, ideally I'd like the kind of behavior to change model validation at runtime, instead of Re-deploying the solution and doing the necessary changes on the model class.

Ideally I'd like this kind of behavior without having to use Microsoft.Practices.EnterpriseLibrary since its been deprecated a couple years back.

The test program I'm currently working in is a .net 6 controller/view project.

Any suggestions relating to .net 6 would be helpful, I've looked in fluentValidation for this, currently not a support feature atm.

Thanks guys.


Solution

  • I found a solution to reading runtime parameters from a config file:

    First, you need to store whatever parameters you need in you config file.

    appsettings.json

    {
      "Account": {
        "PasswordMinLength": 8,
        "PasswordPattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]*$"
      }
    }
    

    Then you need to create a custom Validator attribute class:

    (you can make this class directly in your ViewModel class if you want)

    class MyPasswordValidater : ValidationAttribute
    {
        private readonly int MinimumLength;
        private readonly string MatchPattern;
        private readonly string FieldName;
        public MyPasswordValidater(
            string configFilePath="appsettings.json",
            string fieldName="Password"
        ) : base() {
            using StreamReader r = new(configFilePath);
            string json = r.ReadToEnd();
            dynamic obj = ((dynamic)JsonConvert.DeserializeObject(json))["Account"];
            MinimumLength = (int)obj["PasswordMinLength"];
            MatchPattern = (string)obj["PasswordPattern"];
            FieldName = fieldName;
    
        }
    
        public override bool IsValid(object value)
        {
            if (value == null || value.ToString().Length == 0)
            {
                ErrorMessage = $"{FieldName} is required";
                return false;
            }
            if (value.ToString().Length < MinimumLength)
            {
                ErrorMessage = $"{FieldName} must be at least {MinimumLength} characters";
                return false;
            }
            if (!Regex.IsMatch(value.ToString(), MatchPattern))
            {
                ErrorMessage = $"{FieldName} does not meet requirements";
                return false;
            }
            return true;
        }
    }
    

    Notice in the constructor the value gets pulled from appsettings.json, where it is then stored until validation occurs.

    The last step is giving the attribute to the field you want:

    [MyPasswordValidater()]
    public string Password { get; set; }
    
    [MyPasswordValidater(fieldName: "Confirm Password")]
    public string ConfirmPassword { get; set; }
    

    Since, the constructor doesn't have any required parameters you don't need to pass anything to the validator, but you can change the field name displayed in the error messages

    This will validate required, minimum length, and matching a regular expression (which I used to determine if the password has special characters and upper/lower case)