Согласно Microsoft введите описание ссылки здесь Мне не нужен этот код в методе действия:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Но он не работает для моего случая. У меня есть следующий метод действия контроллера:
[Route("/api/advertisers/create")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<AdvertiserViewModel>> Create([FromBody] AdvertiserViewModel viewModel)
{
var advertiser = new Advertiser
{
Name = viewModel.Name,
AdvertiserRed2Id = viewModel.InnerId.Value
};
await _dbContext.Advertisers.AddAsync(advertiser);
await _dbContext.SaveChangesAsync();
return Ok();
}
И следующая модель представления:
public class AdvertiserViewModel
{
[Required(ErrorMessage = "Empty inner id!!!!!!")]
[JsonProperty("inner_id")]
[DisplayName("inner_id")]
public int ? InnerId { get; set; }
[Required(ErrorMessage = "Empty name!!!!!")]
[JsonProperty("name")]
[DisplayName("name")]
public string Name { get; set; }
}
Но здесь у меня есть две проблемы. 1) Если InnerId
равно нулю, у меня нет сообщений проверки, и. NET Core просто записывает это значение в мою базу данных. Я имею в виду, что JSON не будет выдавать никаких ошибок валидации:
{
"name": "hello!!!",
"inner_ididididididi": 123234
}
Как видите, у меня нет inner_id
здесь. Итак, ссылка из документации говорит al ie?) Или я не понимаю, как это использовать? Нам нужно
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
2) Вторая проблема. Я вижу тенденцию, которую Microsoft пытается сделать все более удобным и простым. Все нормально. Но я не могу найти правильный способ увидеть значения из моих [JsonProperty('some_value')]
для моих проверочных ответов. Что я имею в виду здесь? Например, у нас есть следующий контроллер:
[Route("/api/advertisers/create")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<AdvertiserViewModel>> Create([FromBody] AdvertiserViewModel viewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var advertiser = new Advertiser
{
Name = viewModel.Name,
AdvertiserRed2Id = viewModel.InnerId.Value
};
await _dbContext.Advertisers.AddAsync(advertiser);
await _dbContext.SaveChangesAsync();
return Ok();
}
и следующий запрос:
{
"name": "hello!!!",
"inner_id": null
}
Итак, что я получу в качестве ответа на проверку? Следующее:
{
"InnerId": [
"Empty inner id!!!!!!"
]
}
Это нормально для бритвы и видов. Но мой клиент JS приложение. И клиент не понимает, что такое InnerId
, мой клиент знает только о inner_id
.
Чтобы дать нам возможность очистить наши контроллеры API if (!ModelState.IsValid) ...
- хорошее начало, но почему нет такой простой возможности (получить проверочные ответы в соответствии с полями запроса JSON)? Мне кажется, было бы замечательно, если бы [ApiController]
смог сделать это.
В результате моих исследований у меня есть следующий фильтр:
public class DisplayNameValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.ErrorCount > 0)
{
var modelType = context.ActionDescriptor.Parameters
.FirstOrDefault(p =>
p.BindingInfo.BindingSource.Id.Equals("Body", StringComparison.InvariantCultureIgnoreCase) ||
p.BindingInfo.BindingSource.Id.Equals("Custom", StringComparison.InvariantCultureIgnoreCase)
)
?.ParameterType; //Get model type
var expandoObj = new ExpandoObject();
var expandoObjCollection =
(ICollection<KeyValuePair<String, Object>>)
expandoObj; //Cannot convert IEnumrable to ExpandoObject
var dictionary = context.ModelState.ToDictionary(k => k.Key, v => v.Value)
.Where(v => v.Value.ValidationState == ModelValidationState.Invalid)
.ToDictionary(
k =>
{
if (modelType != null)
{
var property = modelType.GetProperties().FirstOrDefault(p =>
p.Name.Equals(k.Key, StringComparison.InvariantCultureIgnoreCase));
if (property != null)
{
//Try to get the attribute
var displayName = property.GetCustomAttributes(typeof(DisplayNameAttribute), true)
.Cast<DisplayNameAttribute>().SingleOrDefault()?.DisplayName;
return displayName ?? property.Name;
}
}
return k.Key; //Nothing found, return original vaidation key
},
v => v.Value.Errors.Select(e => e.ErrorMessage).ToList() as Object); //Box String collection
foreach (var keyValuePair in dictionary)
{
expandoObjCollection.Add(keyValuePair);
}
dynamic eoDynamic = expandoObj;
context.Result = new BadRequestObjectResult(eoDynamic);
}
base.OnActionExecuting(context);
}
}
И я могу использовать его как :
[DisplayNameValidationFilter]
MyControllerClass : ControllerBase {...}
Но, с моей точки зрения, это хак. Я вынужден скопировать его из проекта в проект. Может быть, есть какое-то хорошее решение из коробки?
Обновление
Я покажу вам мой Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
// options.SuppressMapClientErrors = true;
}).AddNewtonsoftJson();
services.AddAutoMapper(typeof(Startup));
// db context
services.AddEntityFrameworkNpgsql()
.AddDbContext<ApplicationDbContext>();
// services are here
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
Как видите, я использую .AddNewtonsoftJson()
для контроллеров и [JsonProperty(...)]
(пакет Microsoft.AspNetCore.Mvc.NewtonsoftJson
).