简体   繁体   中英

How can I enhance ModelBinder in ASP.NET Core (update property values for a model class)

I would like to enhance final result that ModelBinder returns.
For example:

public class MyModel
{
    public int Order {get;set;}

    [MyUpperCaseAttribute]
    public string Title {get;set;}
}

In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.

For example:

[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
    model.Title should be upper case, even if send from client in lower case.
}

My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.

What would be the best option to achieve desired behaviour?

Important: (edited):
It would be nice if directive attributes could be stacked:

public class MyModel
{
    public int Order {get;set;}
    [MyUpperCaseAttribute]
    [RemoveSpacesAttribute]
    public string Title {get;set;}
}

Edited:
It looks similar to this , but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.

I should say, that it would be nice if JSON case rule would work:

public class MyModel
{
    public int Order {get;set;}
    public string Title {get;set;}
}

should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.

This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.

public class MyModel
{
    private string _title;
    public int Order {get;set;}

    public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}

You can do this thing inside your MyUpperCaseAttribute as follows:

public class MyUpperCaseAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if(value != null)
        {
            validationContext.ObjectType
            .GetProperty(validationContext.MemberName)
            .SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
        }

        return null;
    }
}

Property value will be converted to UpperCase during Model Binding . I have checked it in my side and it works perfectly.

There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters , not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter .

First the attribute:

[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}

Then we decorate our model class with it:

public class MyModel
{
    public int Order { get; set; }

    [ToUppercase]
    public string Title { get; set; }
}

Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:

public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
    private readonly JsonInputFormatter _jsonInputFormatter;

    public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
    {
        _jsonInputFormatter = jsonInputFormatter;

        foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
            SupportedEncodings.Add(supportedEncoding);

        foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
           SupportedMediaTypes.Add(supportedMediaType);
    }

    public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);

        foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
            && p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
        {
            var value = (string)property.GetValue(result.Result.Model);
            property.SetValue(result.Result.Model, value.ToUpper());
        }

        return result;
    }
}

Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:

public static class MvcOptionsExtensions
{
    public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
    {
        if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
        {
            var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
            opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
        }
    }
}

And finally, call that method to effect the replacement in Startup.cs :

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddMvc(options => options.UseToUppercaseJsonInputFormatter());
    }
}

Et voilà!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM