ASP.NET Core Custom Validation Attribute Not Firing - c#

I have a GET method in API Controller. I would like that method to be validated using custom validation attribute as below. However it's not getting fired for some reasons.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CheckValidRoute : ValidationAttribute
{
private readonly string _apiRoute;
private readonly string _operation;
public override bool RequiresValidationContext { get { return true; } }
public CheckValidRoute(string apiRoute, string operation)
{
_apiRoute = apiRoute;
_operation = operation;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Validation logic here
}
}
Controller
public class TestController : ControllerBase
{
[HttpGet("production/{movieId}/Test")]
[ProducesResponseType(typeof(ResponseModel<string>), 200)]
[Authorize(Policy = SecurityConstants.PseudofilmAuthorizationPolicy)]
[CheckValidRoute("production/{movieId}/Test", "GET")]
public async Task<ResponseModel<string>> TestGet(long movieId)
{
return ResponseModelHelper.BuildResponse("Success", $"production/{movieId}/Test", "Test");
}
}
I am not sure what I am missing here.
-Alan-

You are using the wrong base class to implement your attribute.
ValidationAttribute is used on Models to validate their property values. An example of this is the Required attribute.
In your case you want to implement an ActionFilter, which can be applied to a controller method and can perform validation before the method is called. An example for you would be:
public class CheckValidRouteAttribute : ActionFilterAttribute
{
private readonly string _apiRoute;
private readonly string _operation;
public CheckValidRouteAttribute(string apiRoute, string operation) : base()
{
_apiRoute = apiRoute;
_operation = operation;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var request = context.HttpContext.Request;
var method = request.Method;
if (string.Compare(method, _operation, true) != 0)
{
context.Result = new BadRequestObjectResult("HttpMethod did not match");
}
}
}
In the OnActionExecuting method you can perform your checks against the HttpContext.Request object. I have left that as an exercise for you as I don't know your exact requirements :-)
After applying this attribute to your controller method, the OnActionExecuting will be called before the controller method is called so you can do your validation.
You can read more about ActionFilters here

Related

Replacing RouteData value in Asp.Net Core ActionFilter does not affect [ApiController] validation

I am implementing HashIds, with the goal of transparently converting entity Ids into short hashes for use in the API.
The Goal:
A request to/api/tag/dY7je should have the effect of /api/tag/1 (or w/e the has converts out to)
The controller action should still require an int: public ActionResult<TagViewModel> Get(int id)
The Problem:
I've created an ActionFilterAttribute that intercepts the request, and converts the hash to the appropriate id, and changes the RouteData to match. However, even after changing the RouteData any controller with[ApiController] still returns an error saying "The value 'dY7je' is not valid.". If I remove [ApiController], the modified value goes through to the controller action and all is well.
The Question:
What data is [ApiController] looking at that I can modify through my ActionFilter to avoid this issue, and how do I do that? I've searched through the ActionExecutingContext and am unable to find the value there.
My Rough Action Filter:
public class HashIdFilterAttribute : ActionFilterAttribute, IOrderedFilter
{
public new int Order => int.MinValue;
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach ((string paramKey, object value) in context.RouteData.Values)
{
if (paramKey.EndsWith("id", StringComparison.OrdinalIgnoreCase))
{
int id = HashIdsUtility.DecodeInt(value as string);
context.RouteData.Values[paramKey] = HashIdsUtility.DecodeInt(id);
context.ActionArguments[paramKey] = HashIdsUtility.DecodeInt(id);
}
}
base.OnActionExecuting(context);
}
}
The solution was to use a ResourceFilter which executes before any ActionFilters. MSDocs
public class HashIdFilterAttribute : ActionFilterAttribute, IOrderedFilter, IResourceFilter
{
public new int Order => int.MinValue;
public void OnResourceExecuted(ResourceExecutedContext context)
{
return;
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
ProcessRouteData(context.RouteData);
}
private void ProcessRouteData(RouteData routeData)
{
foreach ((string paramKey, object value) in routeData.Values)
{
if (paramKey.EndsWith("id", StringComparison.OrdinalIgnoreCase))
{
routeData.Values[paramKey] = HashIdsUtility.DecodeInt(value as string);
}
}
}
}

How to avoid calling ModelState.IsValid on every PostBack?

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!!

How to get Post parameters on IsAuthorized of AuthorizeAttribute ASP.NET Web.API MVC 5

I need to get values of my Post Parameters at the time of Authorization. Searchers on web but no solution is working. ActionArguments count always showing 0 and not able to find values in ActionDescriptor.GetParameters()
Here is my code:
POST model -
public class XyzModel
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
}
Custom Authorize Attribute -
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
bool conditions = // here I need to check value of my model (XyzModel) properties
if(conditions)
{
return true;
}
return false;
}
}
Code in controller -
[HttpPost]
[CustomAuthorizeAttribute]
public IHttpActionResult MyAction(XyzModel model)
{
// my work here
}
Any suggestion?
You can access model property of ActionArguments it will return XyzModel object. than you can perform any operation on its properties:
XyzModel model = (XyzModel)actionContext.ActionArguments["model"];
In your code it will be like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var prop1 = HttpContext.Current.Request.Params["Prop1"];
var prop2 = HttpContext.Current.Request.Params["Prop2"];
bool conditions = // add conditions based on above properties
if(conditions)
{
return true;
}
return false;
}
}
I believe, you will not get post parameter value in AuthorizeAttribute as AuthorizeAttribute methods are called before action's parameter binding.
For your scenario, you can use ActionFilterAttribute which executes only after action's parameter binding. You can create your custom filter attribute by using ActionFilterAttribute
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class CheckMyPostDataFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
XyzModel model = (XyzModel )actionContext.ActionArguments["model"]; // you will get data here
base.OnActionExecuting(actionContext);
}
}
You can simply decorate above CheckMyPostDataFilter filter in your action :
[HttpPost]
[CheckMyPostData]
public IHttpActionResult MyAction(XyzModel model)
{
// my work here
}
You can use Request Body Input Stream to read the entire Body content as below
public sealed class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var req = HttpContext.Current.Request.InputStream;
string body = new StreamReader(req).ReadToEnd();
}
}

Restrict action filter attribute for one action method [duplicate]

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]

ActionFilterAttribute - apply to actions of a specific controller type

I'm using an ActionFilterAttribute to do custom authentication logic. The Attribute will only be used on a derived Controller class that contains my authentication logic.
Here's my Controller, derived from my custom controller class, and a sample attribute:
public class MyController : CustomControllerBase
{
[CustomAuthorize(UserType = UserTypes.Admin)]
public ActionResult DoSomethingSecure()
{
return View();
}
}
Here's an example of my ActionFilterAttribute:
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public MyUserTypes UserType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
myUser user = ((CustomControllerBase)filterContext.Controller).User;
if(!user.isAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
}
}
Works great.
Here's the question: Can I demand that this attribute ONLY be used on Actions in my custom controller type?
You can put the ActionFilter on the class itself. All actions in the class will realize the ActionFilter.
[CustomAuthorize]
public class AuthorizedControllerBase : CustomControllerBase
{
}
public class OpenAccessControllerBase : CustomControllerBase
{
}
public class MyRealController : AuthorizedControllerBase
{
// GET: /myrealcontroller/index
public ActionResult Index()
{
return View();
}
}
Based on the comments and the constraints of my system, I took a hybrid approach. Basically, if the request comes through via a cached route or the "User" is not set for any reason, authentication fails in the proper way.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private MyUser User { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Lazy loads the user in the controller.
User = ((MyControllerBase)filterContext.Controller).User;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = false;
string retLink = httpContext.Request.Url.AbsolutePath;
if(User != null)
{
isAuthorized = User.IsValidated;
}
if (!isAuthorized)
{
//If the current request is coming in via an AJAX call,
//simply return a basic 401 status code, otherwise,
//redirect to the login page.
if (httpContext.Request.IsAjaxRequest())
{
httpContext.Response.StatusCode = 401;
}
else
{
httpContext.Response.Redirect("/login?retlink=" + retLink);
}
}
return isAuthorized;
}
}

Categories