I am working on an ASP.NET Core 2.0 API that will be consumed by my customers. One of the issues I am running into is that when I use ModelState for input validation of the request payload, the resultant error message that the consumer sees has a [objectPrefix].PropertyName in the response JSON. Our API documentation lists the Property name but not the object class and so the prefix is creating a problem when the consumer writes code that deserializes the JSON response in to their local object model.
Is there any option I can set in Startup.cs ConfigureServices method for Service.AddMvc, or something similar, that will disable this prefix?
I am using the Microsoft.AspNetCore.All(2.0.7) dependency in my API, .NET Core 2.0.4 and VS2016 v15.5.7 if that matters.
I am using Data Annotations from the System.ComponentModel.DataAnnotations lib and decorating my creation DTO class properties like below;
[Required]
[MaxLength(14)]
public string AccountNumber
{
get => _accountNumber;
set => _accountNumber = !string.IsNullOrWhiteSpace(value) ? value.Trim() : string.Empty;
}
When the consumer doesn't provide an account number in the request payload, the error that returns looks like this;
{
"[AccountDto].AccountNumber": [
"The AccountNumber field is required."
]
}
What I want to do is elimintate the [AccountDto]. prefix so that the error JSON then looks like this;
{
"AccountNumber": [
"The AccountNumber field is required."
]
}
I found this SO post but it seems to reference the older ASP.NET.
Currently, I am having my client do a string replace on the json response but I really would like to have a better solution.
Any ideas?
UPDATE 5/16/18
It seems that the issue with the prefix is related to my use of the Validate method in my *ForCreationDtos.
For example,
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (CompanyId == 0)
{
yield return new ValidationResult("A Company ID is required.", new[] { "CompanyId" });
}
}
However, I have found a work around by using a global ModelState handler and modifying it to parse out the prefix.
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);
}
}
This is a bit crude and assumes "[0]." as the prefix but that is the one that I get whenever I implement the Validate method in the DTO class. This seems to have solved my specific issue.
I am using Microsoft.AspNetCore.All
v2.0.8, Microsoft.NETCore.App
v2.0.7 and Visual Studio
Community 2017 v15.7.1, and everything worked out like what you want.
I can't reproduce your issue. I even thought maybe I just created the model within the web project so I even created a separate class project to contain the DTOs. It still works out like what you want!
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();
}
}
}