Attribut ApiController работает не правильно в моем случае и выглядит бесполезным - PullRequest
0 голосов
/ 18 февраля 2020

Согласно 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).

...