Pass an object from ActionFilter.OnActionExecuting() to an ApiController - asp.net-web-api

I wish to create an object per http request within an ActionFilter and pass this object to the controller. So far I have tried Request.Properties[] like the following
public class DbReadonlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Request.Properties["CustomObjectKey"] = new MyClass();
And I have also tried to assign the new object direct to a ControllerBase class.
public class DbReadonlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = (MyControllerBase) actionContext.ControllerContext.Controller;
controller.StorageContextFactory = new MyClass();
The problem is that neither technique delivers an instance of MyClass to the controller because the new Property["CustomObjectKey"] is lost in the Webapi pipeline by the time a controller method is invoked.
The controller is re-instantiated by the webapi pipeline after the call to action filter OnActionExecuting().
Break points confirm the Webapi pipeline schedules the following event flow during a single http request.
constructor MyControllerBase()
MyAuthenticationFilter
Filter OnActionExecuting()
constructor MyControllerBase()
MyController.MethodA()
The double instantiation of MyControler is odd, but right now I am looking for any technique to pass a newly created object from an action filter to a controller.
Edit-1: The MyAuthorizationFilter mentioned in v1 of this question is actually an Authentication filter. Still investigating.
Solution: The bug was in another filter. After I removed my authentication filter the problem reported in this question went away.

You will have to use .add method Request.Properties collection.
public class DbReadonlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Request.Properties.Add(new KeyValuePair<string, object>("CustomObjectKey", new MyClass()));
You can retrieve this value from your api controller.
object _customObject= null;
if (Request.Properties.TryGetValue("CustomObjectKey", out _customObjectKey))
{
MyClass myObject = (MyClass)_customObject;
}

Another way to pass variable from ActionFilter.OnActionExecuting() to an ApiController:
public class CustomFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
actionContext.ControllerContext.RequestContext.RouteData.Values["CustomValue"] = "CustomValue";
}
}
Pay attention to use ActionFilterAttribute for Web API :
System.Web.Http.Filters.ActionFilterAttribute
Not for MVC classic :
System.Web.Mvc.ActionFilterAttribute
Using:
[CustomFilter]
public class SomeController : ApiController
{
string customValue = RequestContext.RouteData.Values.ToDictionary(x => x.Key, y => y.Value)["user_id"].ToString();
//...
}

Related

Call two Action Methods and Combine the responses to produce new response in .NET Web API

I have two versions of an API.
The second version of API will be having only one action method instead of two action methods in first version of API.
Second version of API action method will basically combine responses of first version of API's both action methods and return combined response to client.
Example code as follows:
[ApiController]
[Route("[controller]")]
public class NumbersV1Controller : ControllerBase
{
private readonly ILogger<NumbersV1Controller> _logger;
public NumbersV1Controller(ILogger<NumbersV1Controller> logger)
{
_logger = logger;
}
[HttpGet]
public int Get()
{
return 1;
}
[HttpPost]
public int Post()
{
return 2;
}
}
[ApiController]
[Route("[controller]")]
public class NumbersV2Controller : ControllerBase
{
private readonly ILogger<NumbersV2Controller> _logger;
public NumbersV2Controller(ILogger<NumbersV2Controller> logger)
{
_logger = logger;
}
[HttpPost]
public IEnumerable<int> Get()
{
// Method 1: Make a direct HTTP request.
// int response1 = HTTPClientHelper.GetRequest("Get", "NumbersV1");
// int response2 = HTTPClientHelper.PostRequest("Post", "NumbersV1");
// Method 2: Use instances and set controller context.
NumbersV1Controller numbersV1Controller = new NumbersV1Controller(null);
numbersV1Controller.ControllerContext = this.ControllerContext;
int response1 = numbersV1Controller.Get();
int response2 = numbersV1Controller.Post();
// Method 3: Use RedirectToAction method.
// RedirectToActionResult response1 = RedirectToAction("Get", "NumbersV1");
// RedirectToActionResult response2 = RedirectToAction("Post", "NumbersV1");
return new List<int>() { response1, response2 };
}
}
Method 1: Make a direct HTTP request.
It works perfectly but it is having additional boilerplate code and also it like making a new network call.
Method 2: Use instances and set controller context.
Not sure if this will work perfectly like can I access the Request object in version 1 controller and not sure how to initialize the version 2 controller will multiple injected objects
Method 3: Use RedirectToAction method.
I was assuming RedirectToAction will work but I don't see the result of the Action method in response object RedirectToActionResult.
What are the best options available for doing this in .NET Web API or is there any other way of doing this elegently?
Avoid using method 2 / method 3. Why? It violates so many patterns and performance will be an issue.
Method 1 is average if you really want to do it that way but will cost a network call though.
Method 4:
You can call directly inline business logic code from your V2 controller. If you already separated your business logic code to an individual service then you need to call it from your controller.
I have introduced a new class to do all the logical operations. You might have a similar one / many service classes for handling business requirements.
Let me give you an example:
public class Number1Controller : BaseController {
// You can use DI container to resolve this. I am using this as an example.
private readonly Service _service = new();
[HttpGet("{id}")]
public int GetById(int id) => _service.GetById(id);
[HttpGet("{name}")]
public string GetByName(string name) => _service.GetByName(name);
}
public class Number2Controller : BaseController {
// You can use DI container to resolve this. I am using this as an example.
private readonly Service _service = new();
[HttpGet("{id}")]
public int GetById(int id) => _service.GetById(id);
[HttpGet("{name}")]
public string GetByName(string name) => _service.GetByName(name);
}
// Business Logic Service
public class Service {
public int GetById(int id) => 1;
public string GetByName(string name) => "Stack Over Flow";
}

Attribute routing and inheritance

I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?
Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22
Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)

webapi actionfilters, how to inject a value when using different argument types that inherit from a base type

I have a base request type..
class RequestBase
{
public string inputId;
public string derivedid;
}
and types that inherit ..
class RequestA : RequestBase
{
public string name;
}
and
class RequestB : RequestBase
{
public string color;
}
I have a webapi service, some actions take an input parameter of RequestA, some take RequestB
[HttpPost]
[MyFilter]
[ActionName("Process1")]
public HttpResponseMessage Process1(RequestA request)
{
//do something with request.derivedId
}
[HttpPost]
[MyFilter]
[ActionName("Process2")]
public HttpResponseMessage Process2(RequestB request)
{
//do something with request.derivedId
}
I have an actionfilter that takes the inputId from the request and generates a derivedId
public override void OnActionExecuting(HttpActionContext actionContext)
{
RequestBase request = (RequestBase)actionContext.ActionArguments["request"];
string inputId = request.inputId;
string derivedId = inputId + "123";
// ?? somehow inject derivedId back into the actionContext so that my controller methods can access?
}
As my comment states above, I'd like to populate the derivedId field and have it accessible to my controller methods.
Thanks in advance
There's a few solutions to this problem already described in this thread - one of them should suit you:
ASP.NET MVC Pass object from Custom Action Filter to Action

ASP.NET Web API Ninject constructor injected custom filter and attributes

I'm struggling with getting a custom attribute / filter working with ninject, constructor injection on the ASP.NET Web API.
Here's a few snippets to give some context...
//controller
[ApiAuthorise]
public IEnumerable<Thing> Get()
// Attribute definition with no body
public class ApiAuthoriseAttribute : FilterAttribute {}
// Custom Filter definition
public class ApiAuthoriseFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{ throw new NotImplementedException(); }
}
//Ninject module for my API authorisation
public class ApiAuthoriseModule : NinjectModule
{
public override void Load()
{
this.BindFilter<ApiAuthoriseFilter>(FilterScope.Action, 0)
.WhenActionMethodHas<ApiAuthoriseAttribute>()
}}
//The registerServices(IKernel kernel) method in NinjectMVC3.cs
kernel.Load(new ApiAuthoriseModule());
That's literally all the code I have concerning this filter and attribute.
From what I understand I don't have to explicitly add the filter to the global filter collection as ninject takes care of that, is that correct?
If I place a constructor inside my attribute and throw an exception from within there I can see that the attribute is firing.
My suspicion is something I'm doing wrong within the Ninject side of things but after spending an afternoon reading others examples that appear to be identical to mine I'm know asking for help :)
TIA
There are different classes that you need to work with in Web API, not the standard System.Web.Mvc.FilterAttribute and System.Web.Mvc.IAuthorizationFilter that are used in normal controllers:
public class ApiAuthoriseAttribute : System.Web.Http.Filters.FilterAttribute
{
}
public class ApiAuthoriseFilter : System.Web.Http.Filters.IAuthorizationFilter
{
public System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken, Func<System.Threading.Tasks.Task<HttpResponseMessage>> continuation)
{
throw new NotImplementedException();
}
public bool AllowMultiple
{
get { return false; }
}
}
Then you will obviously have to modify Ninject and the filter binding syntax (BindFilter extension method) to be able to register this new classes. Or wait for Ninject.MVC4 which will include this functionality.

asp.net mvc jsonresult block external use

Is it possible to block any other use of json result and allow just requests from my application ?
when we use something like this:
Json(q, JsonRequestBehavior.AllowGet)
it allow all requests from anywhere.is there any authentication exist to check where request is from ?
I think you mean:
How to allow only AJAX requests?
If so, view the following blog post. It describes creating a reusable filter:
AjaxOnly attribute
The code seems quite simple, but I haven't used it myself:
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.HttpContext.Response.Redirect("/error/404");
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
That you can then apply to controllers and actions:
[AjaxOnly]
public ActionResult AjaxActionMethod()
{
//....
}
The filter code presumes the existence of an action on some controller that can be reached by the following route:
/error/404
As a result, I have amended the code, and produced an easy way of adding an arbitrary error route (with a default value of "/error/404"):
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public AjaxOnlyAttribute(){}
public AjaxOnlyAttribute(string ErrorRoute)
{
this.ErrorRoute = ErrorRoute;
}
string errorRoute = "/Error/404"; // default route
public string ErrorRoute
{
get { return errorRoute; }
set { errorRoute = value; }
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.HttpContext.Response.Redirect(this.ErrorRoute); //
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
This can now be used as follows:
[AjaxOnly(ErrorRoute = "/MyArbitraryRoute/MyArbitraryParameter")
public ActionResult AjaxActionMethod()
{
//....
}
Add the [Authorize] attribute to your methods or controllers that you want to protect. You can specify the group membership and a login will be required.
If you only want a method to be callable by your own application, change the method declaration from public to internal. This will limit the scope of the method to calls from within your application.

Resources