I have written custom attribute to redirect request to https when http is requested for my Web API project as below
public class RedirectToHttpAttribute: AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Found, "");
var uri = new UriBuilder(actionContext.Request.RequestUri);
uri.Scheme = Uri.UriSchemeHttps;
uri.Port = 44326;
response.Headers.Location = uri.Uri;
actionContext.Response = response;
}
}
}
Now I want to set this attributes to all my controllers and actions so I added this in WebApiConfig.
config.Filters.Add(new RedirectToHttpAttribute());
Now there is one controller where I need to allow both http and https. To make this possible I have to remove above line from WebApiConfig and add to all controllers except one in the question. I can do it easily as I have very few controllers but if I had many many controller then what would have been the solution as it is very likely to generate error prone to decorate each controller?
You could do this by creating a second attribute, and modifying your existing redirection filter.
Something like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AllowHttpAttribute : Attribute
{
}
public class RedirectToHttpsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowHttpAttribute>(false).Any())
{
return;
}
// Perform the redirect to HTTPS.
}
}
Then on your controller (or action):
[AllowHttp]
public class ValuesController : ApiController
{
// ...
}
Related
I need to add authorization to a particular route without adding Authorize attribute. Is there any way I can do this in startup? I know I can add Authorize attribute globally to all the routes, but I need to add authorize just to a specific method in a controller without touching any code in that controller.
If you cannot touch code I see the only solution - check using middleware. Lets imagine that route you want to restrict access is POST '/users/register', so you can use ActionFilter registered globally in startup in which you check url and if its url is '/users/register' you are trying to check token and if token is not valid - return 401.
Also you can use Owin middleware
Here is simple example of implementation such logic using ActionFilter
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configuration.Filters.Add(new CheckAuthorizationFilterAttribute());
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
public class CheckAuthorizationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var requestUri = actionContext.Request.RequestUri.AbsolutePath;
if (requestUri == "/api/users/register")
{
var isTokenValid = ValidateToken();
if (!isTokenValid)
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
}
public bool ValidateToken() => false;
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
}
}
I want to be able to inject headers to WebApi controller method context using an ActionFilterAttribute:
public class HeaderInjectionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Request.Headers.Add("test", "test");
base.OnActionExecuting(actionContext);
}
}
and using this in a controller
[HeaderInjectionFilter]
public class MotionTypeController : ApiController
{
public bool Get()
{
// will return false
return HttpContext.Current.Request.Headers.AllKeys.Contains("test");
}
}
As I stated out in the comment the header injected by the Filter will not be part of the HttpContext.Current. When I set a breakpoint on the last line of OnActionExecuting in the attribute I can see that it is containing the header value in the request headers.
If I change my controller to
public class MotionTypeController : ApiController
{
public bool Get()
{
HttpContext.Current.Request.Headers.Add("test", "test");
// will return true
return HttpContext.Current.Request.Headers.AllKeys.Contains("test");
}
}
everything will work so I guess the actionContext of the OnActionExecuting is not the same as the HttpContext.Current of the controller.
How can I inject headers for debugging purposes?
As I stated out in the comment the header injected by the Filter will
not be part of the HttpContext.Current
That's because you added it to the actionContext.Request.Headers collection. So make sure you are looking in the same place where you added it:
[HeaderInjectionFilter]
public class MotionTypeController : ApiController
{
public bool Get()
{
return this.Request.Headers.GetValues("test").Any();
}
}
and just forget about HttpContext.Current. Think of it as something that doesn't exist. Everytime someone uses HttpContext.Current in an ASP.NET application a little kitten dies.
In a Web API or ASP.NET MVC application I can add a Global validation filter by doing -
GlobalConfiguration.Configuration.Filters.Add(new ModelValidationFilterAttribute());
and
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
But I want to have many validations filters that apply to controllers of my choice?
Filters are attributes that can be applied to your controllers or specific methods/actions in your controllers.
To use your filter on a case-by-case basis, you can do either of the following:
[MyFilter]
public class HomeController : Controller
{
//Filter will be applied to all methods in this controller
}
Or:
public class HomeController : Controller
{
[MyFilter]
public ViewResult Index()
{
//Filter will be applied to this specific action method
}
}
This tutorial describes filters in detail and provides examples of both scenarios.
I'm using a custom filter to validate the content type, like:
public override void OnActionExecuting(HttpActionContext httpActionContext)
{
List<String> errors = new List<String>();
// a
if (httpActionContext.Request.Content.Headers.ContentType.MediaType == "application/json")
{
}
else
{
errors.Add("Invalid content type.");
}
// more checks
}
The above code is working fine, but the validation should check the request http verb, because it should validate the content type only for put or post. I don't want to remove the custom filter from httpget actions because I have more checks inside it, and I don't want to split the filter in two parts, meaning I have to check the http verb inside the filter, but I can't find how.
Any tips?
You can get the method type (post or put) from this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
string methodType = actionContext.Request.Method.Method;
if (methodType.ToUpper().Equals("POST")
|| methodType.ToUpper().Equals("PUT"))
{
// Your errors
}
}
If you need to get the HTTP Method of the request being validated by the filter, you can inspect the Method property of the request:
var method = actionContext.Request.Method;
I would recommend however that you break the filter apart, as you are quickly headed towards a big ball of mud scenario.
You really should be using the standard HTTPVerb attributes above your controller methods:
[HttpGet]
[HttpPut]
[HttpPost]
[HttpDelete]
[HttpPatch]
MVC Controllers for multiple:
[AcceptVerbs(HttpVerbs.Get, HttpVerbs.Post)]
WebAPI Controlelrs for multiple
[AcceptVerbsAttribute("GET", "POST")]
In the constructor of the action filter, you can pass in options/named parameters that will set the settings for the OnActionExecuting logic. Based on those settings you can switch up your logic.
public class MyActionFilterAttribute : ActionFilterAttribute
{
private HttpVerbs mOnVerbs;
public MyActionFilterAttribute(HttpVerbs onVerbs)
{
mOnVerbs = onVerbs;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var currentVerb = filterContext.HttpContext.Request.HttpMethod;
if (mOnVerbs.HasFlag(HttpVerbs.Post)) { }
else if (mOnVerbs.HasFlag(HttpVerbs.Get)) { }
base.OnActionExecuting(filterContext);
}
}
[MyActionFilter(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Index()
{
}
I want to be able to mark an action on controller to be called both from ajax calls and via RenderAction. The problem is that both this attributes derive or implement different abstractions. One way out is the next:
[AjaxOnly]
PartialViewResult GetViewAjax(int foo) { return GetView(foo); }
[ChildActionOnly]
PartialViewResult GetView(int foo) { ... }
But this is not neat at all.
The AjaxOnly attribute I am talking about is:
public sealed class AjaxOnlyAttribute : ActionFilterAttribute
{
#region Public members
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] != "XMLHttpRequest")
filterContext.Result = new HttpNotFoundResult();
}
#endregion
}
This method is taken from MVC3 futures. An important comment why the condition is not filterContext.HttpContext.Request.IsAjaxRequest() was made by dev team and says the following:
// Dev10 #939671 - If this attribute is going to say AJAX *only*, then we need to check the header
// specifically, as otherwise clients can modify the form or query string to contain the name/value
// pair we're looking for.
This doesn't make any sense. Those 2 attributes are mutually exclusive. If an action is marked with [ChildActionOnly] it can never be directly accessed by the client using an HTTP request (be it synchronous or asynchronous). So if you want an action to ever be accessible using AJAX, you should never decorate it with the [ChildActionOnly] attribute.
I don't know what this [AjaxOnly] attribute is and where it comes from but depending on how it is implemented you might need to tweak it in order to allow child action requests if it relies only on the Request.IsAjaxRequest() method. For example if it is something like this:
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new HttpNotFoundResult();
}
}
}
you might want to tweak it like this:
public class AjaxOrChildActionOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest() &&
!filterContext.IsChildAction
)
{
filterContext.Result = new HttpNotFoundResult();
}
}
}
Inspired on Darin's answer and ChildActionOnlyAttribute's source code, this is the solution I have come up with, which I think it's a tiny bit better:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AjaxOrChildAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
return controllerContext.IsChildAction || controllerContext.RequestContext.HttpContext.Request.IsAjaxRequest();
}
}
This way, the validation is done before even trying to execute, and the error you get if you type in the url is the exact same one as trying any invalid url.