I am going to create own exceptionfilter which was inherit from FilterAttribute and IExceptionFilter
Source code is given below :
public class IndexException : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext exceptionContext)
{
if (!exceptionContext.ExceptionHandled && exceptionContext.Exception is IndexOutOfRangeException)
{
exceptionContext.Result = new RedirectResult("/Content/ExceptionFound.html");
exceptionContext.ExceptionHandled = true;
}
}
}
But when my code get to Index method where exception generated manually, my filter can't work
[IndexException]
public ActionResult Index()
{
throw new Exception("Не может быть меньше нуля");
You have to register your filter IndexException in the ASP.NET MVC Pipeline via RegisterGlobalFilters in FilterConfig.cs.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// add your filter
filters.Add(new IndexException());
}
}
Your exception filter will only redirect to the ExceptionFound.html if a IndexOutOfRangeException is caught.In the example you provided you are throwing a generic Exception.
Either change your filter to catch all types of exceptions or change this line:
throw new Exception("Не может быть меньше нуля");
To this:
throw new IndexOutOfRangeException("Не может быть меньше нуля");
Related
I pretty much always want to check if ModelSate.IsValid is called when I do a postback. And having to check at the start of every post back violates the DRY principle, is there a way to have it checked automatically?
Example:
[HttpPost("RegisterUser")]
[AllowAnonymous]
public async Task<IActionResult> RegisterUser([FromBody] UserRegisterViewModel vmodel)
{
if(!ModelState.IsValid) // This code is repeated at every postback
return ModelInvalidAction(); // Is there a way to avoid having to write it down?
// do other things
return StatusCode(201);
}
The framework provides an abstract ActionFilterAttribute that you can subclass.
You can use an action filter to automatically validate model state and return any errors if the state is invalid:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
You can either then use it on individual actions or register it globally
Reference Asp.Net Core : Action Filters
You can try something like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
filterContext.Result = new BadRequestResult();
}
}
}
You can request any registered service like this filterContext.HttpContext.RequestServices.GetService<ILogger>().
You can decorate by action filter your action or controller:
[HttpPost("RegisterUser")]
[AllowAnonymous]
[ValidateModel]
public async Task<IActionResult> RegisterUser([FromBody] UserRegisterViewModel vmodel)
{
...
}
I've researched this and found the best answer I think. Even if I implement what's mentioned in the other answers, I'll still be repeating myself by having to put a [ValidateModel] attribute on each POST and PUT request, that's something I want to avoid, I would also like to log things if a model is invalid, other answers don't really allow for this. So here is my answer:
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class ValidateViewModelAttribute : Attribute, IFilterFactory
{
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetService<ILogger>();
return new InternalValidateModel(logger);
}
private class InternalValidateModel : IActionFilter
{
private ILogger _log;
public InternalValidateModel(ILogger log)
{
_log = log;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (IsInvalidModelState(context))
{
_log.Information("Invalid ModelState: {Model}", context.ModelState.ErrorMessages());
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
private bool IsInvalidModelState(ActionExecutingContext context)
{
var method = context.HttpContext.Request.Method;
return (method == "POST" ||
method == "PUT") &&
!context.ModelState.IsValid;
}
}
public bool IsReusable => true;
}
I don't want to repeat myself by having to add a [ValidateViewModel] on every POST and PUT. So I do the following:
services.AddMvc(config =>
{
config.Filters.Add(new ValidateViewModelAttribute());
});
Now all POST and PUT methods are validated!!
I'm using ASP.NET Core and FluentValidation.
When a POST action receives invalid input, it's customary to re-render the input form view, with validation errors:
if (!ModelState.IsValid)
return View("nameOfViewRenderedByGetAction", model);
But my validation is actually performed in a service, by FluentValidation, which throws ValidationException. I want to handle it in an exception filter:
public class ValidationFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// only handle ValidationException
var ex = context.Exception as ValidationException;
if (ex == null) return;
// re-render get action's view, or redirect to get action
// ??
}
}
I'm stuck at the "??" part, because Core has changed the signatures of many types, and ExceptionContext doesn't surface the data I need to make this work.
How do I do this?
It's a little late for an answer but I have a working solution for exactly the same application design. I use ASP.NET Core 3.0 and FluentValidation 8.x.
public class MvcValidationExceptionFilterAttribute : ExceptionFilterAttribute
{
private IModelMetadataProvider ModelMetadataProvider { get; }
public MvcValidationExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
ModelMetadataProvider = modelMetadataProvider;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Framework calls without null")]
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException ex)
{
var validationResult = new ValidationResult(ex.Errors);
validationResult.AddToModelState(context.ModelState, null);
context.Result = new ViewResult { ViewData = new ViewDataDictionary(ModelMetadataProvider, context.ModelState) };
context.ExceptionHandled = true;
}
}
}
As this filter has a dependency we can't use the Attribute directly but register it with dependency injection in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<MvcValidationExceptionFilterAttribute>();
To use the ExceptionFilter either apply it via the ServiceFilterAttribute:
[ServiceFilter(typeof(MvcValidationExceptionFilterAttribute))]
public class MyController : Controller
{
Or apply it globally in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<MvcValidationExceptionFilterAttribute>();
})
From an exception filter, You can render a custom view by setting the context result.
public class ValidationFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// only handle ValidationException
var ex = context.Exception as ValidationException;
if (ex == null) return;
// re-render get action's view, or redirect to get action
var result = new ViewResult { ViewName = "GetView" }
context.HttpContext.Response.Clear();
context.Result = result;
}
}
Where GetView should be the name of your Get action's view.
Sample exception filter that uses a custom developer error view to display details about exceptions.
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.Result = result;
}
}
Note that the above code is sending the context, model state and exception to the view.
In case all you need is custom error page refer to ASP.NET Core Error Handling
Generally, you should not be using an exception filter to turn an error into success. Consider using an action filter if you have a requirement like that.
Having said that, for some reason if you still need to redirect from an exception filter, this is how it can be done
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Error" }));
context.Result = result;
}
}
In my MVC application I am trying to handle errors in my Application_Error method of my HttpApplication. In that handler I do this:
Exception exc = Server.GetLastError();
I'm using Ninject which provides its own DefaultControllerFactory which will throw an exception for non-existent controllers which I can easily catch like this:
if (exc is MyApp.Web.App_Start.ControllerNotFoundException)
{
Response.Clear();
Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
Server.ClearError();
log = false;
}
Which works great. I don't want to log these.
The problem is when the controller does exist, but the action does not. For example, I have somebody hitting: admin/config.php. I actually have an AdminController so that doesn't cause a ControllerNotFoundException, it gives me a HttpException with the text:
"A public action method 'config.php' was not found on controller 'MyApp.Web.Controllers.AdminController'."
But I'm other than parsing the text to detect that it's this type of HttpException and not some other, is there a way to tell this is an action not found rather than something else?
I believe this will do what you want. You can inherit the default AsyncControllerActionInvoker class and then inject it.
public class DependencyResolverForControllerActionInvoker : IDependencyResolver
{
private readonly IDependencyResolver innerDependencyResolver;
public DependencyResolverForControllerActionInvoker(IDependencyResolver innerDependencyResolver)
{
if (innerDependencyResolver == null)
throw new ArgumentNullException("innerDependencyResolver");
this.innerDependencyResolver = innerDependencyResolver;
}
public object GetService(Type serviceType)
{
if (typeof(IAsyncActionInvoker).Equals(serviceType) || typeof(IActionInvoker).Equals(serviceType))
{
return new MyAsyncControllerActionInvoker();
}
return this.innerDependencyResolver.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.innerDependencyResolver.GetServices(serviceType);
}
}
public class MyAsyncControllerActionInvoker : AsyncControllerActionInvoker
{
public override bool InvokeAction(ControllerContext controllerContext, string actionName)
{
try
{
return base.InvokeAction(controllerContext, actionName);
}
catch (HttpException ex)
{
// Handle unknown action error
}
}
public override bool EndInvokeAction(IAsyncResult asyncResult)
{
try
{
return base.EndInvokeAction(asyncResult);
}
catch (HttpException ex)
{
// Handle unknown action error
}
}
}
Here is a link to the InvokeAction and EndInvokeAction methods so you can try to determine how best to handle any errors it throws.
Usage
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Decorate the current dependency resolver
// (make sure to do this last if using a DI container -
// or alternatively register your type with the DI container)
DependencyResolver.SetResolver(
new DependencyResolverForControllerActionInvoker(DependencyResolver.Current));
}
}
Alternative
You could create a base controller and override the HandleUnknownAction method for a similar (but more tightly coupled) result.
I write this code in several places and always repeat this logic:
public ActionResult MyMethod(MyModel collection)
{
if (!ModelState.IsValid)
{
return Json(false);//to read it from javascript, it's always equal
}
else
{
try
{
//logic here
return Json(true);//or Json(false);
}
catch
{
return Json(false);//to read it from javascript, it's always equal
}
}
}
Is there any way using action filters, not to be repeating the try-catch, ask if the model is valid and return Json(false) as ActionResult?
To conform with REST, you should return http bad request 400 to indicate that the request is malformed (model is invalid) instead of returning Json(false).
Try this attribute from asp.net official site for web api:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
A version for asp.net mvc could be like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
}
If you want to do this in MVC6 or Mvc Core and without specifying your attribute on all of your Action methods then this is how you do it.
First create your ActionFilter
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext context )
{
if ( context.HttpContext.Request.Method == "POST" && !context.ModelState.IsValid )
context.Result = new BadRequestObjectResult( context.ModelState );
}
}
Now create a convention in which you will apply this ActionFilter to all of your controllers.
public class ModelStateValidatorConvension : IApplicationModelConvention
{
public void Apply( ApplicationModel application )
{
foreach ( var controllerModel in application.Controllers )
{
controllerModel.Filters.Add( new ModelStateValidationFilterAttribute() );
}
}
}
And the last thing is to register this convention in MVC
public void ConfigureServices( IServiceCollection services )
{
services.Configure<MvcOptions>( x => x.Conventions.Add( new ModelStateValidatorConvension() ) );
}
Starting from ASP.Net Core 2.1, the [ApiController] attribute will trigger automatic model validation:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Consequently, the following code is unnecessary in action methods or custom ActionFilterAttribute:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Source: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.1#automatic-http-400-responses-1
Here is how to use the code from Khanh TO (from asp.net official site):
To apply this filter to all Web API controllers, add an instance of the filter to the HttpConfiguration.Filters collection during configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Another option is to set the filter as an attribute on individual controllers or controller actions:
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ViewResult();
}
}
}
I Register a GlobalFilters for HandleErrorAttribute:
public class AppHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception ex = filterContext.Exception;
//TODO
//LogManager.GetLogger("Exception").Error(ex.Message);
if (filterContext.Exception is UserException){
if(!string.isNullOrEmpty(this.View))
{
filterContext.ExceptionHandled = true;
filterContext.Result = ...;//<===this.View(custom Page)
}
else{
filterContext.ExceptionHandled = true;
filterContext.Result = ...;//<==='XYZ' page(another custom page)
}
}
}
}
And In Web.Config Set:
<customErrors mode="On"/>
Edit Begin
And In FilterConfig I set:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new AppHandleErrorAttribute() );
}
End
Then I just want the Action of 'Test()' to run AppHandleErrorAttribute for Once.
public class XXXController:Controller{
public ActionResult Test()
{
throw new UserException("test0x11", "test", null);
return View();
}
[AppHandleError(View="Index")]//<=======here I want the Test2 to Index View, but it will be call AppHandleError twice this time
//it always Redirect to 'XYZ' page
public string Test2()
{
throw new UserException("test0x12", "test", null);
return "haha";
}
public string Index(){...}
}
How can I do not call globle HandleError?