I want to return my custom message when take invalid values my model.Therefore I use the Fluentvalidation.Create validators and asyncActionfilter. But Method returned default fluent message instead of my custom message. Why?
My AsyncActionFilter Class
public class ModelValidatorFilterAttribute : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
var errorMessages =
context.ModelState.Values
.Select(
x =>
new ExceptionResponse.ExceptionMessage { Message = x.Errors.First().ErrorMessage })
.ToList();
throw new ValidationException(errorMessages);
}
await next();
}
}
My Validator
public class MyValidator : AbstractValidator<MyRequestDto>
{
public MyValidator()
{
RuleFor(s => s.ProductId).NotEmpty().NotNull()
.WithMessage(x => "my custom message");
}
}
My Startup config
services.AddFluentValidation();
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Response
{
"exceptionMessages": [
{
"message": "'Product Id' must not be empty.",
"duration": 0,
"type": "Type"
}
]
}
Related
I have created custom 'ValidationFilter' in order to validate request before it reaches controller, there it is:
ValidationFilter class
using Contracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Middleware.Filters
{
public class ValidationFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
#pragma warning disable CS8602 // Dereference of a possibly null reference.
var errorsInModelState = context.ModelState
.Where(o => o.Value.Errors.Count > 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Errors.Select(o => o.ErrorMessage)).ToArray();
#pragma warning restore CS8602 // Dereference of a possibly null reference.
var errorResponse = new ErrorResponse();
foreach(var error in errorsInModelState)
{
foreach(var subError in error.Value)
{
var errorModel = new ErrorModel
{
FieldName = error.Key,
Message = subError
};
errorResponse.Errors.Add(errorModel);
}
}
context.Result = new BadRequestObjectResult(errorResponse);
return;
}
await next();
}
}
}
There is simple Validator for request:
using Contracts;
using FluentValidation;
namespace Middleware.Validators
{
public class AddressingDtoValidator : AbstractValidator<AddressingDto>
{
public AddressingDtoValidator()
{
RuleFor(x => x.District)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Mr)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Quarter)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Street)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Building)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Corpus)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Building)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.InstitutionName)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
}
}
}
I have also "ErrorModel" and "ErrorResponse" classes which you can see below and my goal is to display error using this class but somemwhy it doesn't work:
ErrorModel
namespace Contracts.ViewModels
{
public class ErrorModel
{
public string? FieldName { get; set; }
public string? Message { get; set; }
}
}
ErrorResponse class
namespace Contracts.ViewModels
{
public class ErrorResponse
{
public List<ErrorModel> Errors { get; set; } = new List<ErrorModel>();
}
}
I want the error to be displayed like this:
{
"errors": [
{
"fieldname": "....",
"message": "...."
}
]
}
but instead, i get something like that:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-b9157268dc1f793004182694c1acf1a7-67fd063ebec5cbc3-00",
"errors": {
"Quarter": [
"'Quarter' must not be empty."
]
}
}
I mean this type of error is understandable and okay to read but i am practicing with action filters and something is not working.
There is also Program.cs class where i inject this validator in pipeline:
builder.Services.AddControllers(options =>
{
options.Filters.Add<ValidationFilter>();
})
.AddFluentValidation(configuration => configuration.RegisterValidatorsFromAssemblyContaining<AddressingDtoValidator>());
So what could be the problem, am i missing something?
To disable the default model state handling, you need to add the below setting in Program.cs.
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I have successfully add custom header using IOperationFilter in my .net core application, now my problem is how to filter it out for certain methods only in the SomeController class. Is this achievable? This is my current code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
// some swagger services here.
c.OperationFilter<SomeFilter>();
});
}
SomeFilter.cs
public class SomeFilter: IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "Some-Custom-Header",
In = ParameterLocation.Header,
Required = false,
Schema = new OpenApiSchema
{
Type = "String"
}
});
}
}
SomeController.cs
[ApiController]
[Route("[controller]")]
public class SomeController: Controller
{
[AllowAnonymous]
[HttpPost("some_method1")]
public IAction SomeMethod1() // this method should not include custom header filter
{
return Ok();
}
[Authorize]
[HttpPost("some_method2_with_authorize")]
public IAction SomeMethod2() // this should have the custom header in swagger
{
return Ok();
}
[AllowAnonymous]
[HttpGet("some_method3_without_authorize")]
public IAction SomeMethod3() // this should have the custom header in swagger
{
return Ok();
}
}
I found a way on how to exclude the method that will exclude the said methods by doing this:
public class SomeFilter: IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var methodName = context.ApiDescription.ActionDescriptor.As<ControllerActionDescriptor>().ActionName;
var hasExcludedMethod = ApiFilterSettings.AppSettingsFilterMethods.ToStringList().Contains(methodName);
if(hasExcludedMethod)
return; // return directly when an excluded method is found;**strong text**
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "Some-Custom-Header",
In = ParameterLocation.Header,
Required = false,
Schema = new OpenApiSchema
{
Type = "String"
}
});
}
}
I hope this will help you guys.
I need to sent custom exceptions message to client.
I have the following code:
in Startup.cs ConfigureServices method
services.AddGrpc(options => options.Interceptors.Add<ErrorInterceptor>());
in ErrorInterceptor.cs
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (ValidationException validationExc)
{
await WriteResponseHeadersAsync(StatusCode.InvalidArgument, translation =>
translation.GetEnumTranslation(validationExc.Error, validationExc.Parameters));
}
catch (Exception)
{
await WriteResponseHeadersAsync(StatusCode.Internal, translation =>
translation.GetEnumTranslation(HttpStatusCode.InternalServerError));
}
return default;
Task WriteResponseHeadersAsync(StatusCode statusCode, Func<ITranslationService, string> getMessage)
{
var httpContext = context.GetHttpContext();
var translationService = httpContext.RequestServices.GetService<ITranslationService>();
var errorMessage = getMessage(translationService);
var responseHeaders = new Metadata
{
{ nameof(errorMessage) , errorMessage },//1) can see in browser's devTools, but not in the code
{ "content-type" , errorMessage },//2) ugly, but works
};
context.Status = new Status(statusCode, errorMessage);//3) not working
return context.WriteResponseHeadersAsync(responseHeaders);//4) alternative?
}
}
in mask-http.service.ts
this.grpcClient.add(request, (error, reply: MaskInfoReply) => {
this.grpcBaseService.handleResponse<MaskInfoReply.AsObject>(error, reply, response => {
const mask = new Mask(response.id, response.name);
callback(mask);
});
});
in grpc-base.service.ts
handleResponse<T>(error: ServiceError,
reply: {
toObject(includeInstance?: boolean): T;
},
func: (response: T) => void) {
if (error) {
const errorMessage = error.metadata.headersMap['content-type'][0];
this.toasterService.openSnackBar(errorMessage, "Ok");
console.error(error);
return;
}
const response = reply.toObject();
func(response);
}
I wanted to send error using Status (comment 3), but it doesn't get changed
I wonder if there is an alternative way to send it not in response headers (comment 4)
I tried to add custom response header (comment 1), but the only one I received in client code was 'content-type' so I decided to overwrite it (comment 2)
I hit the same dead end recently and decided to do it this way:
Create an error model:
message ValidationErrorDto {
// A path leading to a field in the request body.
string field = 1;
// A description of why the request element is bad.
string description = 2;
}
message ErrorSynopsisDto {
string traceTag = 1;
repeated ValidationErrorDto validationErrors = 2;
}
Create an extension for the error model that serializes the object to JSON:
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public static class ErrorSynopsisDtoExtension
{
public static string ToJson(this ErrorSynopsisDto errorSynopsisDto) =>
JsonConvert.SerializeObject(
errorSynopsisDto,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
Create a custom exception that encapsulates error model:
public class OperationException : Exception
{
private readonly List<ValidationErrorDto> validationErrors = new();
public bool HasValidationErrors => this.validationErrors.Count > 0;
public OperationException(string traceTag) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag
}.ToJson() // <- here goes that extension
) => ErrorTag = traceTag;
public OperationException(
string traceTag,
List<ValidationErrorDto> validationErrors
) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag,
ValidationErrors = { validationErrors }
}.ToJson() // <- here goes that extension again
)
{
ErrorTag = traceTag;
this.validationErrors = validationErrors;
}
}
Throw custom exception from service call handlers:
throw new OperationException(
"MY_CUSTOM_VALIDATION_ERROR_CODE",
// the following block can be simplified with a mapper, for reduced boilerplate
new()
{
new()
{
Field = "Profile.FirstName",
Description = "Is Required."
}
}
);
And lastly, the exception interceptor:
public class ExceptionInterceptor : Interceptor
{
private readonly ILogger<ExceptionInterceptor> logger;
public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger) => this.logger = logger;
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation
)
{
try
{
return await continuation(request, context);
}
catch (OperationException ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
if (ex.HasValidationErrors)
{
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
else
{
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
var opEx = new OperationException("MY_CUSTOM_INTERNAL_ERROR_CODE");
throw new RpcException(
new Status(
StatusCode.Internal,
opEx.Message
)
);
}
}
}
On the TypeScript-based frontend, I simply catch RPC errors and hydrate the message like this:
JSON.parse(err.message ?? {}) as ErrorSynopsisDto
Hello I am trying to get custom validation response for my webApi using .NET Core.
Here I want to have response model like
[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
I have a validator class and currently we just check ModalState.IsValid for validation Error and pass on the modelstate object as BadRequest.
But new requirement wants us to have ErrorCodes for each validation failure.
My sample Validator Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
I can use something similar in my actions to get validation result
Opt1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
and manipulate ValidationResult to my customn Response object.
or
Opt2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
but for Opt2 I don't know how to retrieve ValidationResult from interceptor to controller action.
All I want is to write a common method so that I avoid calling Opt1 in every controller action method for validation.
Request to point me to correct resource.
try with this:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I validate the model with fluentvalidation, after build the BadResquest response in a ActionFilter class:
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage)
.ToList();
var responseObj = new
{
Message = "Bad Request",
Errors = errors
};
context.Result = new JsonResult(responseObj)
{
StatusCode = 400
};
}
}
}
In StartUp.cs:
services.AddMvc(options =>
{
options.Filters.Add(typeof(ValidateModelStateAttribute));
})
.AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>());
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
And it works fine. I hope you find it useful
As for me, it's better to use the following code in ASP.NET Core project
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = c =>
{
var errors = string.Join('\n', c.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage));
return new BadRequestObjectResult(new
{
ErrorCode = "Your validation error code",
Message = errors
});
};
});
Also take into account that instead of anonymous object you can use your concrete type. For example,
new BadRequestObjectResult(new ValidationErrorViewModel
{
ErrorCode = "Your validation error code",
Message = errors
});
In .net core you can use a combination of a IValidatorInterceptor to copy the ValidationResult to HttpContext.Items and then a ActionFilterAttribute to check for the result and return the custom response if it is found.
// If invalid add the ValidationResult to the HttpContext Items.
public class ValidatorInterceptor : IValidatorInterceptor {
public ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result) {
if(!result.IsValid) {
controllerContext.HttpContext.Items.Add("ValidationResult", result);
}
return result;
}
public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext) {
return validationContext;
}
}
// Check the HttpContext Items for the ValidationResult and return.
// a custom 400 error if it is found
public class ValidationResultAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext ctx) {
if(!ctx.HttpContext.Items.TryGetValue("ValidationResult", out var value)) {
return;
}
if(!(value is ValidationResult vldResult)) {
return;
}
var model = vldResult.Errors.Select(err => new ValidationErrorModel(err)).ToArray();
ctx.Result = new BadRequestObjectResult(model);
}
}
// The custom error model now with 'ErrorCode'
public class ValidationErrorModel {
public string PropertyName { get; }
public string ErrorMessage { get; }
public object AttemptedValue { get; }
public string ErrorCode { get; }
public ValidationErrorModel(ValidationFailure error) {
PropertyName = error.PropertyName;
ErrorMessage = error.ErrorMessage;
AttemptedValue = error.AttemptedValue;
ErrorCode = error.ErrorCode;
}
}
Then in Startup.cs you can register the ValidatorInterceptor and ValidationResultAttribute like so:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
services.AddMvc(o => {
o.Filters.Add<ValidateModelAttribute>()
});
}
}
Refer this link for answer: https://github.com/JeremySkinner/FluentValidation/issues/548
Solution:
What I've done is that I created a basevalidator class which inherited both IValidatorInterceptor and AbstractValidator. In afterMvcvalidation method if validation is not successful, I map the error from validationResult to my custom response object and throw Custom exception which I catch in my exception handling middleware and return response.
On Serialization issue where controller gets null object:
modelstate.IsValid will be set to false when Json Deserialization fails during model binding and Error details will be stored in ModelState. [Which is what happened in my case]
Also due to this failure, Deserialization does not continue further and gets null object in controller method.
As of now, I have created a hack by setting serialization errorcontext.Handled = true manually and allowing my fluentvalidation to catch the invalid input.
https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [defined OnErrorAttribute in my request model].
I am searching for a better solution but for now this hack is doing the job.
Similar to Alexander's answer above, I created an anonymous object using the original factory I could find in the source code, but just changed out the parts to give back a custom HTTP response code (422 in my case).
ApiBehaviorOptionsSetup (Original factory)
services.AddMvcCore()
...
// other builder methods here
...
.ConfigureApiBehaviorOptions(options =>
{
// Replace the built-in ASP.NET InvalidModelStateResponse to use our custom response code
options.InvalidModelStateResponseFactory = context =>
{
var problemDetailsFactory = context.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState, statusCode: 422);
var result = new UnprocessableEntityObjectResult(problemDetails);
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
});
Here I tried
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage)
.ToList();
var value = context.ModelState.Keys.ToList();
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>();
foreach (var modelStateKey in context.ModelState.Keys.ToList())
{
string[] arr = null ;
List<string> list = new List<string>();
foreach (var error in context.ModelState[modelStateKey].Errors)
{
list.Add(error.ErrorMessage);
}
arr = list.ToArray();
dictionary.Add(modelStateKey, arr);
}
var responseObj = new
{
StatusCode="400",
Message = "Bad Request",
Errors = dictionary
};
context.Result = new BadRequestObjectResult(responseObj);
return;
}
await next();
}
Response Model:
{
"statusCode": "400",
"message": "Bad Request",
"errors": {
"Channel": [
"'Channel' must not be empty."
],
"TransactionId": [
"'TransactionId' must not be empty."
],
"Number": [
"'Number' must not be empty."
]
}
}
I have an application in ASP.NET Core MVC (dnx46) RC1 with an AuthorizationHandler:
public class AppSumAuthAuthorizationHandler : AuthorizationHandler<AppSumAuthRequirement>
{
private readonly IUserRepository _userRepository;
private readonly IUserRoleRepository _userRoleRepository;
public AppSumAuthAuthorizationHandler(IUserRepository userRepository, IUserRoleRepository userRoleRepository)
{
_userRepository = userRepository;
_userRoleRepository = userRoleRepository;
}
protected override async void Handle(AuthorizationContext context, AppSumAuthRequirement requirement)
{
await HandleAsync(context,requirement);
}
protected override async Task HandleAsync(AuthorizationContext context, AppSumAuthRequirement requirement)
{
var currentUserName = context.User.Identity.Name;
var currentUser = await _userRepository.GetAsync(u => u.UserName == context.User.Identity.Name);
// Create user that does not yet exist
if(currentUser == null)
{
var user = new User(currentUserName);
/* Temporary add SysAdmin role */
using(new CreatedBySystemProvider(_userRepository))
{
_userRepository.Add(user);
await _userRepository.SaveChangesAsync();
if (string.Equals(currentUserName, #"BIJTJES\NilsG", StringComparison.CurrentCultureIgnoreCase))
{
user.AddRole(1);
}
currentUser = await _userRepository.GetAsync(u => u.Id == user.Id);
}
}
var resource = (Microsoft.AspNet.Mvc.Filters.AuthorizationContext) context.Resource;
var controllerActionDescriptor = resource.ActionDescriptor as ControllerActionDescriptor;
var controllerName = controllerActionDescriptor.ControllerName;
var actionName = controllerActionDescriptor.Name;
string moduleName;
try
{
// Get the name of the module
moduleName = ((ModuleAttribute)controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(false).First(a => a.GetType().Name == "ModuleAttribute")).ModuleName;
}
catch(InvalidOperationException ex)
{
context.Fail();
throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
}
var access = new Access(moduleName, controllerName, actionName);
if (await currentUser.HasPermissionTo(UrlAccessLevel.Access).OnAsync(access))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
The requirement class is empty:
public interface IAppSumAuthRequirement : IAuthorizationRequirement
{
}
public class AppSumAuthRequirement : IAppSumAuthRequirement
{
}
The Module attribute is also nothing special:
public class ModuleAttribute : Attribute
{
public string ModuleName { get; private set; }
public ModuleAttribute(string moduleName)
{
ModuleName = moduleName;
}
public override string ToString()
{
return ModuleName;
}
}
The exception filter:
public class JsonExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.HttpContext.Response.StatusCode = 500;
context.Result = new JsonResult(new Error
{
Message = exception.Message,
InnerException = exception.InnerException?.InnerException?.Message,
Data = exception.Data,
ErrorCode = exception.HResult,
Source = exception.Source,
Stacktrace = exception.StackTrace,
ErrorType = exception.GetType().ToString()
});
}
}
and policy are configured in my Startup.cs:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new JsonExceptionFilterAttribute());
options.ModelBinders.Insert(0, new NullableIntModelBinder());
}).AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
});
// Security
services.AddAuthorization(options =>
{
options.AddPolicy("AppSumAuth",
policy => policy.Requirements.Add(new AppSumAuthRequirement()));
});
}
and the policy is set on all controllers, by inheriting BaseController:
[Authorize(Policy = "AppSumAuth")]
public class BaseController : Controller
{
public BaseController()
{
}
}
So, in my handler, I get the controllername, actionname and modulename (from the attribute set on the controllers):
[Module("Main")]
When this attribute is not set on a controller, I would like to catch the exception and report this back to the developer calling the controller and deny access. To do this, I've added:
catch(InvalidOperationException ex)
{
context.Fail();
throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
}
The JsonExceptionFilter is called perfectly when there is an exception in the controllers. It is however not called when there is an error in the AuthorizationHandler.
So the question:
How can I get the Exceptions to be caught by the JsonExceptionFilter?
What am I doing wrong?
Solution:
Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// For Windows Auth!
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseExceptionHandler(AppSumExceptionMiddleware.JsonHandler());
app.UseMvc();
}
And my middleware:
public class AppSumExceptionMiddleware
{
public static Action<IApplicationBuilder> JsonHandler()
{
return errorApp =>
{
errorApp.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>();
if (exception != null)
{
var exceptionJson = Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(new AppSumException(exception.Error),
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})
);
context.Response.ContentType = "application/json";
await context.Response.Body.WriteAsync(exceptionJson, 0, exceptionJson.Length);
}
});
};
}
}
Action filter can be used as a method filter, controller filter, or global filter only for MVC HTTP requests. In your case you need to use a middleware, as
Middleware is component that "sit" on the HTTP pipeline and examine
all requests and responses.
As you want to works with exception, you may use ready-to-use ExceptionHandler middleware:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // for example
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
// custom logic
}
});
});