Model state error filter - c#

On my DTO objects I have several attributes to check it's validity
And I catch such a body response when validation is failed
{
"TransactionId": [
"Max length is 20"
],
"AdditionalInfo": [
"Additional Info has to be no longer than 30 chars"
]
}
But I need to unify all the errors to be with "Error" key.
Something like that
{
"Error": [
"Max length is 20",
"Additional Info has to be no longer than 30 chars"
]
}
I wrote special filter and registered it in Startup.cs
public class ModelStateErrorHandlingFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
context.ModelState.SetModelValue("Errors", new ValueProviderResult(new StringValues(context.ModelState.ToString())));
context.Result = new BadRequestObjectResult(context.ModelState);
}
else
{
await next().ConfigureAwait(false);
}
}
}
But nothing changes. I also have tried to change the key, but it has privat setter

You would need to provide you own custom IActionResult or build the desired object model and pass it to an appropriate ObjectResult.
public class ModelStateErrorHandlingFilter : IAsyncActionFilter {
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
if (!context.ModelState.IsValid) {
var model = new {
Error = context.ModelState
.SelectMany(keyValuePair => keyValuePair.Value.Errors)
.Select(modelError => modelError.ErrorMessage)
.ToArray()
};
context.Result = new BadRequestObjectResult (model);
} else {
await next().ConfigureAwait(false);
}
}
}
setting context.Result will short-circuit the request and pass it your custom response with desired content.

Related

FluentValidation use custom IActionFilter

I have a custom IActionFilter which I register with my application like so:
services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));
The class looks like this:
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
public int Order { get; set; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception == null) return;
var attempt = Attempt<string>.Fail(context.Exception);
if (context.Exception is AttemptException exception)
{
context.Result = new ObjectResult(attempt)
{
StatusCode = exception.StatusCode,
};
}
else
{
context.Result = new ObjectResult(attempt)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
}
context.ExceptionHandled = true;
}
}
I would expect that when validating it would invoke the OnActionExecuting method. So I added this code:
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
And I put a breakpoint at the start of the method, but when I run my application and try to post an invalid model, I get this response:
{
"errors": {
"Url": [
"'Url' is invalid. It should start with 'https://www.youtube.com/embed'",
"'Url' is invalid. It should have the correct parameter '?start='"
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|87e96062-42181357ba1ef8c5."
}
How can I force FluentValidation to use my filter?
When [ApiController] attribute is applied ,ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body :
Automatic HTTP 400 responses
To disable the automatic 400 behavior, set the SuppressModelStateInvalidFilter property to true :
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
The best solution I found was:
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var messages = context.ModelState.Values
.Where(x => x.ValidationState == ModelValidationState.Invalid)
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
.ToList();
return new BadRequestObjectResult(
Attempt<string>.Fail(
new AttemptException(string.Join($"{Environment.NewLine}", messages))));
};
})

asp.net core middleware can't catch newtonsoft exception

i use middleware catch request exception and write response like this
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex.Message);
await HandleExceptionAsync(context, ex); //write response
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
FanjiaApiResultMessage resultMessage = new FanjiaApiResultMessage()
{
ResultCode = -1,
Data = null,
Msg = exception.Message
};
string result = JsonConvert.SerializeObject(resultMessage);
context.Response.ContentType = "application/json;charset=utf-8";
if (exception is QunarException)
{
context.Response.StatusCode = (int)(exception as QunarException).httpStatusCode;
}
else
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(result);
}
a request model param like this
public class FlightModel {
[JsonProperty("depCity", Required = Required.Always)]
public string DepCity { get; set; }
}
public IActionResult Test(FlightModel model){
return Content("test");
}
when i post the FlightModel without DepCity , i will get the exception
{
"errors": {
"": [
"Required property 'depCity' not found in JSON. Path '', line 6, position 1."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "8000000a-0003-ff00-b63f-84710c7967bb"
}
Obviously the exception are not catched by middleware.
why middleware is not catch?
An Aspnet Core Model Validation failure does not throw an exception. It provides it's own response with a status code of 400 (Bad request) in a default format.
There are a few ways to override this including a custom attribute: https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/
It looks like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ValidationFailedResult(context.ModelState);
}
}
}
And then is added like so:
[Route("api/values")]
[ValidateModel]
public class ValuesController : Controller
{
...
}
Or you can control the response generation by overriding the InvalidModelStateResponseFactory, like in this SO question: How do I customize ASP.Net Core model binding errors?
Here is an example:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new MyCustomBadRequestObjectResult(actionContext.ModelState);
});

How to query into LUIS programmatically

By default this is how can we send text to LUIS for processing and returns intents.
[Serializable]
public class LuisDialogController : LuisDialog<FAQConversation>
{
private readonly BuildFormDelegate<FAQConversation> _newConversation;
public LuisDialogController(BuildFormDelegate<FAQConversation> newConversation) : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
this._newConversation = newConversation;
}
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
}
I am wondering how can I send text to LUIS programmatically.
//pseudocode
var foo = new Luis();
var luisIntent = foo.processLanguage("How are you?");
switch(luisIntent)
{
case LuisIntent.Inquiry:
{
//do something; break;
}
default:
{
//do something else; break;
}
}
I've been looking in this solution, however he did not answer by giving a regex.
Would the idea be possible?
In publish section of your LUIS model you have "Resources and Keys" subsection
Below "Endpoint" column you have url(s) that may be used to retrieve data from LUIS by http GET:
https://*.api.cognitive.microsoft.com/luis/v2.0/apps/
*?subscription-key=*&verbose=true&timezoneOffset=0&q=this%20is%20test%20sentence
It will provide you JSON result with structure similar to this:
{
"query": "this is test sentence",
"topScoringIntent": {
"intent": "None",
"score": 0.522913933
},
"intents": [
...
],
"entities": []
}
See more detail and sample C# code here.
Alternatively you may use:
using Microsoft.Bot.Builder.Luis;
...
var model = new LuisModel() {};
var luisService = new LuisService(model);
var result = await luisService.QueryAsync(textToAnalyze, CancellationToken.None);

Customize automatic response on validation error

With asp.net core 2.1 an ApiController will automatically respond with a 400 BadRequest when validation errors occur.
How can I change/modify the response (json-body) that is sent back to the client? Is there some kind of middleware?
I´m using FluentValidation to validate the parameters sent to my controller, but I am not happy with the response that I am get. It looks like
{
"Url": [
"'Url' must not be empty.",
"'Url' should not be empty."
]
}
I want to change the response, cause we have some default values that we attach to responses. So it should look like
{
"code": 400,
"request_id": "dfdfddf",
"messages": [
"'Url' must not be empty.",
"'Url' should not be empty."
]
}
The ApiBehaviorOptions class allows for the generation of ModelState responses to be customised via its InvalidModelStateResponseFactory property, which is of type Func<ActionContext, IActionResult>.
Here's an example implementation:
apiBehaviorOptions.InvalidModelStateResponseFactory = actionContext => {
return new BadRequestObjectResult(new {
Code = 400,
Request_Id = "dfdfddf",
Messages = actionContext.ModelState.Values.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
});
};
The incoming ActionContext instance provides both ModelState and HttpContext properties for the active request, which contains everything I expect you could need. I'm not sure where your request_id value is coming from, so I've left that as your static example.
To use this implementation, configure the ApiBehaviorOptions instance in ConfigureServices:
serviceCollection.Configure<ApiBehaviorOptions>(apiBehaviorOptions =>
apiBehaviorOptions.InvalidModelStateResponseFactory = ...
);
Consider creating of custom action filer, e.g.:
public class CustomValidationResponseActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = new List<string>();
foreach (var modelState in context.ModelState.Values)
{
foreach (var error in modelState.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var responseObj = new
{
code = 400,
request_id = "dfdfddf",
messages = errors
};
context.Result = new JsonResult(responseObj)
{
StatusCode = 400
};
}
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
You can register it in ConfigureServices:
services.AddMvc(options =>
{
options.Filters.Add(new CustomValidationResponseActionFilter());
});

.Net Filter For Wrapping JsonResult Actions Response

I've built a Web API application and found an issue (which currently treated badly in my code), the issue summarized in wrapping all Json objects which returned from All API actions with custom nodes(roots).
i.e: I have this json (array) response:
[
{
"Category": "Pages",
"Users": [
{
"ID": "1",
"Fname": "Foo",
"Lname": "Bar"
}
]
}
]
And Need this response:
{
"Object": {
"Body": [
{
"Category": "Pages",
"Users": [
{
"ID": "1",
"Fname": "Foo",
"Lname": "Bar"
}
]
}
]
}
}
So here I just wrapped the response inside {"Object":{"Body": <Response Here>}}
And this I need it to be applied on all API Json responses of type Array.
And for simple Json object response, I need it just to be wrapped like {"Object": <Response Here>}
I wrapped the Json response currently in each controller action by this code:
public JsonResult Categories()
{
return Json(new { Object= new { Body= GetCategoriesList() } }, JsonRequestBehavior.AllowGet);
}
Sure this achievement is so bad because I have to repeat this wrapping in each action.
My Question Is:
How to create ActionFilterAttribute to be called after each action execution to wrap the response as per the above Json sample?
i.e. for creating the filter:
public class JsonWrapper: System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
i.e. for calling the filter:
[JsonWrapper]
public class APIController : Controller
And also to set the response content type in the same filter "application/json"
If suppose here if what you looking for:
public class JsonWrapperAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuted(ActionExecutedContext context)
{
//Check it's JsonResult that we're dealing with
JsonResult jsonRes = context.Result as JsonResult;
if (jsonRes == null)
return;
jsonRes.Data = new { Object = new { Body = jsonRes.Data } }
}
}
Here is how you can use it:
[JsonWrapper]
public JsonResult Index()
{
var data = new
{
a = 1,
b = 2
};
return Json(data, JsonRequestBehavior.AllowGet);
}
Result will be:
{"Object":{"Body":{"a":1,"b":2}}}
To prevent yourself having to repeat wrapping in each action you could either write an extension method which would do the wrapping for you
public static class ControllerExtensions
{
public static JsonResult WrappedJson(this Controller controller, object data, JsonRequestBehavior behavior)
{
return new JsonResult
{
Data = new { Object = new { Body = data } },
JsonRequestBehavior = behavior
};
}
}
or create a new ActionResult class (and add extension methods to return that)
public class WrappedJsonResult : JsonResult
{
public new object Data
{
get
{
if (base.Data == null)
{
return null;
}
return (object) ((dynamic) base.Data).Object.Body;
}
set { base.Data = new {Object = new {Body = value}}; }
}
}

Categories