I am creating a class that derive from AuthorizeAttribute class. I need to pass the parameter in my controller, where I decorate the derive class. How can I achieve that in this situation?
[SampleAuthorization]
public ActionResult GetFileContent(Guid planId)
{
}
public class PlanAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// how can I use my planId to my custom plan authorizaton
base.OnAuthorization(filterContext);
}
}
You can find the values in authorizationContext.RequestContext.HttpContext or if planId is part of the route you can even find it in authorizationContext.RequestContext.RouteData.
Related
I have a BaseController which is like below.
public class BaseController : Controller
{
public string BDynamicConnectionString { get; set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Getting dynamic connectionstring
this.BDynamicConnectionString = GetDynamicConnetionString(this.BCCompanyId, this.BCCompanyIdentifier);
}
}
I have inherited this base controller in my controller as below.
public class TestController : BaseController
{
private ClassroomBL objClassroomBL;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
this.objClassroomBL = ClassObjects.ClassroomBLObject(BDynamicConnectionString);
}
public ActionResult FetchDefaultStatuses()
{
this.objClassroomBL.FetchDefaultStatuses(145);
return View();
}
}
As it is visible I have OnActionExecuting(ActionExecutingContext filterContext) on both BaseController and TestController.
Actually Why I did it like this is I am managing many things in base controller which are needed in each controller inherited from this.
But now I need something like OnActionExecuting(ActionExecutingContext filterContext) on particular controller because I have to initialize object before calling action and object which I have to initialize it uses a property which is initialized in BaseController.
If I am adding OnActionExecuting(ActionExecutingContext filterContext) on both then first, controller which is inheriting BaseController on this event fires before the BaseController. I know this happening because of OOPS concept.
Is anything else I can add on Controller which fires after Basecontrollers OnActionExecuting but before action execution of particular controller .
Thanks!
Just use following in the first line of overrided method:
base.OnActionExecuting(filterContext)
See also base keyword.
So what I have is a base controller that the following [Route] definition
[Route("{application}/api/[controller]")]
public class BaseController
{
}
All of my current controllers inherit from BaseController.
What I am trying to achieve is that two different application can call my controllers and my code to be aware of what 'application' is calling it.
Application 1 should be able to call /Application1/Api/MyController
Application 2 should be able to call /Application2/Api/MyController
and both requests should go to the same controller but my code should be aware of which application called it.
I thought about having some sort of Middleware and then work out the application from the Request.Path, and then store it in something like HttpContext.Current.Items but that doesn't seem like the correct way to do it.
My personal preference here would be to pass the value as an HTTP header rather than a route parameter, especially if you want it everywhere. It means you don't need a Route attribute and a different URL per application. Using a custom ActionFilterAttribute, there's a bunch of ways you can pass this detail into your action. For example:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue("Application", out var values))
{
// Method 1: This allows you to specify a parameter on your action
context.ActionArguments.Add("application", values.First());
// Method 2: This adds the value into the route data
context.RouteData.Values.Add("Application", values.First());
// Method 3: This will set a property on your controller
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = values.First();
}
}
base.OnActionExecuting(context);
}
}
And apply it to action methods or your controller:
[Application]
public class FooController : Controller
{
}
Method 1 Usage:
public IActionResult Index(string application)
{
// do something with the parameter passed in
}
Method 2 Usage:
public IActionResult Index(string application)
{
var application = (string)RouteData.Values["Application"];
}
Method 3 Usage:
First, create a base controller that contains the property:
public abstract class BaseApplicationController : Controller
{
public string Application { get; set; }
}
Then make sure your controller inherits from it:
[Application]
public class FooController : BaseApplicationController
{
}
Now you can access the property on your controller:
public IActionResult Index(string application)
{
var application = this.Application;
}
Bonus Method 4:
As an aside, you could use this method to use the URL route value, using the base controller from method 3, modify the attribute to look like this:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = (string)context.RouteData.Values["application"];
}
base.OnActionExecuting(context);
}
}
Add a route attribute to your controller:
[Route("{application}/api/[controller]/[action]")]
And now you should have the property value on the controller set.
You could move the route template into action and then each action would technically be aware of its caller context by your proposed convention:
[Route("api/[controller]")]
public class YourController : BaseController
{
[HttpGet("{application}")]
public IActionResult Get(string application)
{
if (application == "Application1")
{
...Application1 called
}
if (application == "Application2")
{
...Application2 called
}
...
}
}
Of course, this is your proposed convention and it is not enforced through some custom application authentication in any way so you will have to trust that your callers will correctly identify themselves through this convention.
Another approach, could be to have a base class variable and set that after inspecting the route.
[Route("{application}/api/[controller]")
public class BaseController: Controller
{
protected string CallingApp { get; set; }
public override void OnActionExecuting(ActionExecutingContext ctx)
{
CallingApp = ctx.RouteData.Values["application"];
base.OnActionExecuting(ctx);
}
}
What are my options for passing variables into my MyCustomAttribute class?
Currently the class is defined as:
public class MyCustomAttribute : ActionFilterAttribute
{
public string MyIDParam { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myID = filterContext.ActionParameters[MyIDParam] as Int32?;
// logic
}
}
Now in my controller I have this:
[MyCustomAttribute(MyIDParam = "id")]
public ActionResult Report(int id)
{
Guid userId = GetUserInfo();
// logic
}
In this case I would like to be able to pass id and userId to MyCustomAttribute class.
Is this possible? Doesn't the MyCustomAttribute get executed before the contents of the Report method?
What are my options for passing variables into my MyCustomAttribute class?
Well you're writing a custom ActionFilter which MVC passes the ActionExecutingContext. That variable contains a ton of information at your disposal.
public class MyCustomAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myID = filterContext
.RouteData["id"] as int?
var user = filterContext.Controller.User;
}
}
In this case I would like to be able to pass "id" and "userId" to MyCustomAttribute class.
No you can't pass them in, you have to retreive them from the ActionExecutingContext.
Doesn't the MyCustomAttribute get executed before the contents of the Report method?
Maybe? Depends on which method your override.
The ActionFilterAttribute has a few different points at which it executes. Here is a break down:
ActionFilterAttribute.OnActionExecuting() // Action Filter
public ActionResult Report(int id) // MVC Action is Called
ActionFilterAttribute.OnActionExecuted() // Action Filter
ActionFiltrAttribute.OnResultExecuting() // Action Filter
ActionResult.Execute() // MVC ActionResult Execute is Called
ActionFilterAttribute.OnResultExecuted() // Action Filter
You can see that these methods have been available to access to all this data all the way back since asp.net-mvc version 1.
You can gain that ability by adding support for referencing action parameters within your custom attribute.
[MyCustomAttribute("The parameter for id is {id}")]
public ActionResult Report(int id)
{
Guid userId = GetUserInfo();
... logic ...
}
At runtime, your custom attribute can substitute the value of the referenced parameter for the placeholder.
So for doing this ability, you need only to hook into the ActionExecuting method to grab those actions parameters:
private IDictionary<string, object> _parameters;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_parameters = filterContext.ActionParameters;
base.OnActionExecuting(filterContext);
}
Then inside your OnActionExecuted method, you can create a variable to processed field. Then you can go through each parameter in the dictionary and substitute in the parameter's value:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var data = MyIDParam;
foreach(var kvp in _parameters)
{
data = data.Replace("{" + kvp.Key + "}",
kvp.Value.ToString());
}
}
I have an attribute on my Controller
[ABC]
MyController
The Attribute checks something but I only want to check once per page.
public class ABCAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
////Do some check
}
The OnActionExecuting fires for every element on the page. Partials etc.
How do I check to see if the filterContext is the main page and not a child resource on the page?
public class VerificationAttribute : ActionFilterAttribute
{
public VerificationAttribute ()
{
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//We don't care about children/partials
if (filterContext.IsChildAction)
return;
}
Ignore, think I miss read your question
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.