Extending ModelState to output my own message - how do I get model state value

落爺英雄遲暮 提交于 2019-12-24 19:40:23

问题


I wrote my own extension for ModelStateDictionary, but I am running in to the problem of displaying the inputted value.

Here is my attempt:

public static string ToLoggingFormat(this ModelStateDictionary modelState)
{
    var sb = new StringBuilder();
    sb.AppendLine("Input validation error occurred:");

    foreach (var ms in modelState.Values)
    {
        foreach (ModelError error in ms.Errors)
        {
            sb.AppendLine(error.ErrorMessage);
        }

        if (!string.IsNullOrEmpty(ms.AttemptedValue)) { 
            sb.AppendLine($"Attempted Value: {ms.AttemptedValue}");
        }
    }

    return sb.ToString();
}

My problem is that ms.AttemptedValue is always null. Any ideas please? I have tried ms.RawValue also but that is also null. The value I am passing in is invalid because it is over the limit and therefore not null.


I am adding a custom XML Input formatter as suggested!

public class ModelStateXmlInputFormatter : XmlSerializerInputFormatter
{
    public ModelStateXmlInputFormatter(MvcOptions options) : base(options)
    {
    }
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        var result = await base.ReadRequestBodyAsync(context);
        foreach (var property in context.ModelType.GetProperties())
        {
            var propValue = property.GetValue(result.Model, null);
            var propAttemptValue = property.GetValue(result.Model, null)?.ToString();
            context.ModelState.SetModelValue(property.Name, propValue, propAttemptValue);
        }

        return result;
    }

I add it like so,

services.AddMvc(options =>
{
    // Remove JSON input
    options.OutputFormatters.RemoveType(typeof(JsonOutputFormatter));
    options.InputFormatters.RemoveType(typeof(JsonInputFormatter));
    options.ReturnHttpNotAcceptable = true;

    // Add our customer XmlInputFormatter
        options.InputFormatters.Add(new ModelStateXmlInputFormatter(options));
        options.OutputFormatters.Add(new XmlSerializerOutputFormatter());

})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

But the extension still returns null for ms.AttemptedValue. Any ideas? :))

This is how I am using the extension:

[HttpPost]
[Route("myrequest")]
public async Task<IActionResult> MyRequest([FromBody] MyRequestType request)
{
    if (!ModelState.IsValid) {
          Logger.LogThis(ModelState.ToLoggingFormat());
    }
}

回答1:


For this issue, it happens for JsonInputFormatter. In Asp.Net Core, there are two ways to bind requst to model, FromForm with model binding and FromBody with JsonInputFormatter. For model binding, it calls context.ModelState.SetModelValue to set value for RawValue and AttemptedValue. But, JsonInputFormatter does not implement this feature.

You need to implement this feature in by custom JsonInputFormatter like

  1. ModelStateJsonInputFormatter

    public class ModelStateJsonInputFormatter : JsonInputFormatter
    {
        public ModelStateJsonInputFormatter(ILogger<ModelStateJsonInputFormatter> logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcJsonOptions jsonOptions) : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
        {
        }
        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var result = await base.ReadRequestBodyAsync(context);
            foreach (var property in context.ModelType.GetProperties())
            {
                var propValue = property.GetValue(result.Model, null);
                var propAttemptValue = property.GetValue(result.Model, null)?.ToString();
                context.ModelState.SetModelValue(property.Name, propValue, propAttemptValue);
            }
            return result;
        }
    }
    
  2. Register ModelStateJsonInputFormatter

    services.AddMvc(opt => {
        var serviceProvider = services.BuildServiceProvider();
        var modelStateJsonInputFormatter = new ModelStateJsonInputFormatter(
                    serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ModelStateJsonInputFormatter>(),
                    serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
                    serviceProvider.GetRequiredService<ArrayPool<char>>(),
                    serviceProvider.GetRequiredService<ObjectPoolProvider>(),
                    opt,
                    serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value
            );
        opt.InputFormatters.Insert(0, modelStateJsonInputFormatter);
    }).SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Latest);
    



回答2:


Try this:

foreach (var ms in modelState.Keys)
    {
        var value = modelState[ms];
        foreach (ModelError error in value.Errors)
        {
            sb.AppendLine(error.ErrorMessage);
        }

        if (!string.IsNullOrEmpty(value.Value.AttemptedValue)) { 
            sb.AppendLine($"Attempted Value: {value.Value.AttemptedValue}");
        }
    }

Not tested...



来源:https://stackoverflow.com/questions/57891941/extending-modelstate-to-output-my-own-message-how-do-i-get-model-state-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!