【问题标题】:How can I remove the ModelState prefix on error messages in ASP.NET Core 2.0?如何删除 ASP.NET Core 2.0 中错误消息的 ModelState 前缀?
【发布时间】:2018-05-02 16:47:50
【问题描述】:

我正在开发一个可供客户使用的 ASP.NET Core 2.0 API。我遇到的一个问题是,当我使用 ModelState 对请求有效负载进行输入验证时,消费者看到的结果错误消息在响应 JSON 中有一个 [objectPrefix].PropertyName。我们的 API 文档列出了属性名称,但没有列出对象类,因此当消费者编写将 JSON 响应反序列化到其本地对象模型的代码时,前缀会产生问题。

我可以在 Startup.cs ConfigureServices 方法中为 Service.AddMvc 或类似的设置任何选项来禁用此前缀吗?

如果重要的话,我在我的 API、.NET Core 2.0.4 和 VS2016 v15.5.7 中使用 Microsoft.AspNetCore.All(2.0.7) 依赖项。

我正在使用 System.ComponentModel.DataAnnotations 库中的数据注释并装饰我创建的 DTO 类属性,如下所示;

    [Required]
    [MaxLength(14)]
    public string AccountNumber
    {
        get => _accountNumber;
        set => _accountNumber = !string.IsNullOrWhiteSpace(value) ? value.Trim() : string.Empty;
    }

当消费者在请求载荷中没有提供账号时,返回的错误是这样的;

{
    "[AccountDto].AccountNumber": [
        "The AccountNumber field is required."
    ]
}

我想要做的是消除 [AccountDto]。前缀,以便错误 JSON 看起来像这样;

{
    "AccountNumber": [
        "The AccountNumber field is required."
    ]
}

我发现了这个SO 帖子,但它似乎引用了旧的 ASP.NET。

目前,我正在让我的客户对 json 响应进行字符串替换,但我真的很想有一个更好的解决方案。

有什么想法吗?

2018 年 5 月 16 日更新

似乎前缀的问题与我在 *ForCreationDtos 中使用 Validate 方法有关。

例如,

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {

        if (CompanyId == 0)
        {
            yield return new ValidationResult("A Company ID is required.", new[] { "CompanyId" });
        }

    }

但是,我找到了一种解决方法,方法是使用全局 ModelState 处理程序并对其进行修改以解析出前缀。

public class ValidateModelAttribute : ActionFilterAttribute
{

    /// <summary>
    /// Validates model state upon action execution
    /// </summary>
    /// <param name="context">ActionExecutingContext object</param>
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.ModelState.IsValid) return;
        var errorList = context.ModelState.Where(ms => ms.Value.Errors.Any()).ToDictionary(
            kvp => kvp.Key.Replace("[0].", ""),
            kvp => kvp.Value.Errors.Select(e => string.IsNullOrEmpty(e.ErrorMessage) ? e.Exception.Message : e.ErrorMessage).ToArray()
        );
        var globalErrorDto = new GlobalErrorDto { Errors = errorList };
        context.Result = new BadRequestObjectResult(globalErrorDto);
    }
}

这有点粗略,假设为“[0]”。作为前缀,但这是我在 DTO 类中实现 Validate 方法时得到的前缀。这似乎解决了我的具体问题。

【问题讨论】:

    标签: c# .net-core asp.net-core-2.0 modelstate asp.net-core-mvc-2.0


    【解决方案1】:

    我正在使用 Microsoft.AspNetCore.All v2.0.8、Microsoft.NETCore.App v2.0.7 和 Visual Studio Community 2017 v15.7.1,一切都如你所愿。

    屏幕截图 #1:没有帐号 - 400

    屏幕截图 #2:帐号太长 - 400

    屏幕截图 #3:有效帐号 - 201

    我无法重现您的问题。我什至想也许我只是在 Web 项目中创建了模型,所以我什至创建了一个单独的类项目来包含 DTO。它仍然如你所愿!

    DTO

    using System.ComponentModel.DataAnnotations;
    
    namespace DL.SO.ModelState.Dto.Users
    {
        public class AccountModel
        {
            [Required]
            [MaxLength(14)]
            [Display(Name = "account number")]
            public string AccountNumber { get; set; }
        }
    }
    

    控制器

    using DL.SO.ModelState.Dto.Users;
    using Microsoft.AspNetCore.Mvc;
    
    namespace DL.SO.ModelState.Controllers
    {
        [Route("api/[controller]")]
        public class UsersController : ControllerBase
        {
            [HttpGet("{id}")]
            public IActionResult GetById(string id)
            {
                // Just testing 
                return Ok(id);
            }
    
            [HttpPost]
            public IActionResult Post(AccountModel model)
            {
                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
    
                // Just testing so I pass in null
                return CreatedAtAction(nameof(GetById), 
                     new { id = model.AccountNumber }, null);
            }
        }
    }
    

    启动

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace DL.SO.ModelState
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc();
            }
    
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseMvc();
            }
        }
    }
    

    【讨论】:

    • 在我的 DTO 中,我使用 Validate 方法 (IValidatableObject interace) 检查整数属性值以确保它们不为零。我相信前缀来自这种方法。 public IEnumerable Validate(ValidationContext validationContext) { if (CompanyId == 0) { yield return new ValidationResult("A Company ID is required.", new[] { "CompanyId" }); } }
    • 我将把它标记为答案,因为它让我意识到问题通常不是来自 ModelState,而是来自 Validate 方法,如我的 OP 更新中所述。
    • 很高兴你发现了这一点!这是一个离题的想法:您使用过 FluentValidation 库吗?一般来说,我在 DTO 中没有验证逻辑,因为它们是数据传输对象。相反,我在域对象中进行了验证 - 代表您的业务模型/角色。如果你有时间,你可以看看那个 FluentValidation 库。挺好看的!
    • @David_Liang 我的待办事项列表中有 FluentValidation。我注意到它仍然使用 ModelState,所以我不知道它是否会对前缀问题产生任何影响。但是,正如您所说,DTO 应该用于传输,因此我计划实施 FV 并将验证移出 DTO。谢谢。
    猜你喜欢
    • 2013-06-10
    • 2012-05-16
    • 1970-01-01
    • 1970-01-01
    • 2018-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    相关资源
    最近更新 更多