ASP.NET Core and ActionFilter
Asked Answered
B

1

6

I move old MVC 5 application to Core, old application has code:

public class ValidateApiModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (var key in actionContext.ModelState.Keys)
            {
                result.Add(key, String.Join(", ", actionContext.ModelState[key].Errors.Select(p => p.ErrorMessage)));
            }
            // 422 Unprocessable Entity Explained
            actionContext.Response = actionContext.Request.CreateResponse<Dictionary<string, string>>((HttpStatusCode)422, result);
        }
    }
}

so, it means, that if model state is not valid, then we return dictionary with errors and 422 status code (client's requirements).

I try to rewrite it the following way:

[ProducesResponseType(422)]
public class ValidateApiModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (var key in context.ModelState.Keys)
            {
                result.Add(key, String.Join(", ", context.ModelState[key].Errors.Select(p => p.ErrorMessage)));
            }

            // 422 Unprocessable Entity Explained
            context.Result = new ActionResult<Dictionary<string, string>>(result);
        }
    }
}

but it can't be compiled:

Cannot implicitly convert type Microsoft.AspNetCore.Mvc.ActionResult<System.Collections.Generic.Dictionary<string, string>> to Microsoft.AspNetCore.Mvc.IActionResult

how to do it?

Bruni answered 1/12, 2018 at 18:40 Comment(6)
If you can use ASP.NET Core 2.1, this is done automatically by the frameworkAbstracted
@CamiloTerevinto my guess would be that they don't want the default 400 response and instead want custom 422 status response.Blanche
@CamiloTerevinto what does automatically? I need return exactly this status code with exactly dataBruni
Return new object result and set status code.Blanche
@Blanche I need exactly 422 status codeBruni
@OlegSh Nkosi is right. Just use ObjectResult instead of using ActionResult directlyAbstracted
B
9

Contrary to belief ActionResult<TValue> is not derived from IActionResult. Thus the error.

Return new ObjectResult and set status code as desired.

[ProducesResponseType(422)]
public class ValidateApiModelStateAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext context) {
        if (!context.ModelState.IsValid) {
            var result = new Dictionary<string, string>();
            foreach (var key in context.ModelState.Keys) {
                result.Add(key, String.Join(", ", context.ModelState[key].Errors.Select(p => p.ErrorMessage)));
            }

            // 422 Unprocessable Entity Explained
            context.Result = new ObjectResult(result) { StatusCode = 422 };
        }
    }
}
Blanche answered 1/12, 2018 at 20:2 Comment(2)
For some reason, for me, this does not trigger context.ModelState.IsValid to be false even when the properties of the model are null or an empty string. So the 422 result never gets set.Sicard
Update: If anyone else does the foolish mistake I did - I was missing the AddDataAnnotations() bit after services.AddMvcCore() - https://mcmap.net/q/1773639/-modelstate-isvalid-always-true-regardless-of-dataannotations-attributesSicard

© 2022 - 2024 — McMap. All rights reserved.