asp net core web api custom model validation - asp.net-core-webapi

Asp net core web API, I am trying to return a custom response for the model validator. But ValidateModelFilter is not called when the required field, not in the request.
ValidateModelFilter.cs
public class ValidateModelFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var response = new Model
{
error = context.ModelState.ToString()
};
context.Result = new BadRequestObjectResult(response);
}
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => options.Filters.Add(typeof(ValidateModelFilter)));
}
I am getting a response like this
{
"errors": {
"Firstname": [
"The Firstname field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|e6752549-4142e91f1074e978."
}
I want to return a response like
{
"error": "The Firstname field is required."
}

The link shows how we have to disable the default Model Binding Exception filter that is returning a 400 error and short circuits the pipeline. Only then the OnActionExecuting method in our custom ActionFilterAttribute is actually getting executed. Everything then works as expected.
https://stackoverflow.com/a/51522487/14924779

Related

Micronaut Bean Validation with Optional Parameters

I am currently using Micronaut with the following request bean and controller:
...
#Introspected
public final class Bean {
private final HttpRequest<?> httpRequest;
#Nullable
#Positive
private final Integer value;
...
}
...
#Controller("/api/example")
public class ExampleController {
#Get
#Produces(MediaType.APPLICATION_JSON)
public HttpResponse<?> read(#Valid #RequestBean Bean bean) {
Map<String, Object> content = Map.of(
"bean", bean.toString()
);
return HttpResponse.ok(content);
} //read
}
The issue I am having is that value does not seem to be validated when a non-integral value is provided, such as "test". Instead, value is set to null in the bean.
I was hoping to make value optional, hence the #Nullable annotation. Is there any easy way to detect that a non-integral value was passed in for value and send back an error? If I provide a negative number, a nice error message is sent back for me:
{
"message": "Bad Request",
"_links": {
"self": {
"href": "/api/example?value=-1",
"templated": false
}
},
"_embedded": {
"errors": [
{
"message": "bean.value: must be greater than 0"
}
]
}
}
Thanks!

MediatR not resolving in an api controller

I have added MediatR to OnApplicationStarted in global.asax
But it's not resolving for my controller.
It returns an error:
{
"Message": "An error has occurred.",
"ExceptionMessage": "An error occurred when trying to create a controller of type 'NotificationApiController'. Make sure that the controller has a parameterless public constructor.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Type 'MyDomain.MyProject.Controllers.NotificationApiController' does not have a default constructor",
"ExceptionType": "System.ArgumentException",
"StackTrace": " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
The global.asax:
var builder = new ContainerBuilder();
/* MVC Controllers */
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
builder.RegisterModelBinderProvider();
/* WebApi Controllers */
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
/* Umbraco Controllers */
builder.RegisterControllers(typeof(UmbracoApplication).Assembly);
builder.RegisterApiControllers(typeof(UmbracoApplication).Assembly);
/* Custom Api Controllers */
builder.RegisterApiControllers(typeof(Controllers.SearchResultsApiController).Assembly);
builder.RegisterModule<WebApiConfig>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver =
new AutofacWebApiDependencyResolver(container);
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always;
WebApiConfig:
public class WebApiConfig : Module
{
protected override void Load(ContainerBuilder builder)
{
// Register custom types with Autofac
/* Third-party types */
// This didn't work so I added the below line with the explicit handler
builder.AddMediatR(this.GetType().Assembly);
// But it didn't make any difference
builder.AddMediatR(typeof(Index).Assembly);
/* Umbraco context types */
ApplicationContext applicationContext = ApplicationContext.Current;
builder.RegisterInstance(applicationContext.Services.ContentService)
.As<IContentService>();
builder.RegisterInstance(applicationContext.Services.MemberService)
.As<IMemberService>();
//builder.Register(c => UmbracoContext.Current).AsSelf();
builder.Register(c => UmbracoContext.Current).InstancePerRequest();
builder.Register(x => new UmbracoHelper(UmbracoContext.Current))
.InstancePerRequest();
}
}
The controller:
public class SearchResultsApiController : UmbracoApiController
{
private readonly IMediator _mediator;
public SearchResultsApiController(IMediator mediator)
{
_mediator = mediator;
}
}
I'm using .NET 4.7.2 (and Umbraco 7.15.3 if that matters).
The issue was that I had api controllers in two separate projects so probably the resolver couldn't find the right one.
If api controllers in one project were working, the controllers in the second project were failing showing the above error.
I have consolidated all api controllers in one project and now everything works fine.

ASP.NET CORE 2: Model bound complex types must not be abstract or value types and must have a parameterless constructor

I want to use IOptions to get configuration via POCO but it throws the error message 'Model bound complex types must not be abstract or value types and must have a parameterless constructor'
DatabaseSettings.cs
public class DatabaseSettings
{
public string MongoDBServer { get; set; }
}
appsettings.json
"DatabaseSettings": {
"MongoDBServer": "localhost"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<DatabaseSettings>(Configuration.GetSection("DatabaseSettings"));
}
Controller
public IActionResult Create(IOptions<DatabaseSettings> options)
{
string test = options.Value.MongoDBServer;
return View();
}
I don't know how to handle it. Do you have an advise for me? Thanks!
I had the same issue. I found this answer helpful.
Please try to add [FromServices] to the controller action:
public IActionResult Create([FromServices] IOptions<DatabaseSettings> options)

How to configure ASP.NET Core to handle circular references without breaking the body's response?

I have this ASP.NET Core 2.0 MVC Controller:
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet("[action]")]
public Example Demo()
{
return new Example("test");
}
public class Example
{
public Example(string name)
{
Name = name;
}
public string Name { get; }
public IEnumerable<Example> Demos
{
get { yield return this; }
}
}
}
When querying /api/SampleData/Demo, I get as response body:
{"name":"test","demos":[
...which is obviously very broken JSON-like output.
How and where do I have to configure my ASP.Net Core 2.0 MVC-based app to make the framework serialize circular references in a way that does not break the output? (For example, by introducing $ref and $id.)
In order to switch on references for JSON.Net serialization, you should set PreserveReferencesHandling property of SerializerSettings to PreserveReferencesHandling.Objects enum value.
In ASP.Net Core you could do it by following adjustment in Startup.ConfigureServices method:
services.AddMvc()
.AddJsonOptions(opt =>
{
opt.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
});
Now the model will be serialized to following correct JSON:
{
"$id": "2",
"name": "test",
"demos": [ { "$ref": "2" } ]
}

Json.Net Deserialization - Web API and Nullable Date

Say you've got a model that looks like
public class UserModel
{
public string UserName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The DateOfBirth field isn't required, but could be specified. You have a Web API POST endpoint that looks like
[Route("")]
[AcceptVerbs("POST")]
public async Task<IHttpActionResult> Create(UserModel user)
{
}
And we've set the JSON serializer in start up like so,
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new IsoDateTimeConverter());
settings.Error += (sender, args) => Console.WriteLine("This event is fired ok");
}
If we send some JSON to the endpoint that looks like this
{
"userName": "User1",
"dateOfBirth": "jhdgjhjfg"
}
...the error event is fired in the Serializer settings and the endpoint is called. At this point, the DateOfBirth field is null and I don't have any context that a deserialization error has occurred
Reading the JSON.Net documentation, because Handled == false in the Error event arguments of the Settings object, an exception should be raised into the application code - this doesn't happen? Is there a setting I haven't configured correctly for this?
How can I get context within the action so that I know a value was specified for a field and couldn't be deserialized? Even global behaviour would be fine, as long as I know this has happened and can return a 400.
UPDATE:
We can use a filter to check the Model state, then check the Model State errors for exceptions of type JsonReaderException. This lets you return a 400 with a list of violating fields
public class CheckJsonExceptionModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
var fieldsInError = new List<string>();
foreach (var jsonException in
actionContext.ModelState.Keys.SelectMany(key => actionContext.ModelState[key].Errors)
.Select(error => error.Exception).OfType<JsonReaderException>())
{
Trace.TraceError(jsonException.Message);
fieldsInError.Add(jsonException.Path);
}
var apiError = new { ErrorMessages.BadRequestModel.Message, FieldsInError = new List<string>() };
foreach (var fieldError in fieldsInError)
{
apiError.FieldsInError.Add(fieldError);
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
You have multiple options. But first, you are getting no exception because the WebApi handles this exception. Bad news.
Good news, you can handle it in at least two ways; use the ModelState.IsValid property - in your case it will be false. You can access them in your post-method. When you remove the invalid dateOfBirth it is true ;-)
Or you can use an ActionFilterAttribute to put it on your methods for re-use purposes.
For example:
public async Task<IHttpActionResult> Create(UserModel user) {
if (!ModelState.IsValid) {
// ModelState.Keys // Get all error-keys
}
}

Resources