I have a base controller:
public abstract class EntityController<T> : ApiController
{
[HttpPost]
[ValidateModel]
public abstract IHttpActionResult Create(T dto);
[HttpPut]
[ValidateModel]
public abstract IHttpActionResult Update(T dto);
[HttpDelete]
public abstract IHttpActionResult Delete(int id);
}
And a derived controller:
public class CustomerTypeController : EntityController<CustomerTypeDTO>
{
[ApiAuthorize(Right = Rights.CustomerType, Action = Action.Create)]
public override IHttpActionResult Create(CustomerTypeDTO customerType)
{
return Save(customerType);
}
[ApiAuthorize(Right = Rights.CustomerType, Action = Action.Update)]
public override IHttpActionResult Update(CustomerTypeDTO customerType)
{
return Save(customerType);
}
private IHttpActionResult Save(CustomerTypeDTO customerType)
{
//service layer save customer type
}
}
The base controller has a [ValidateModel] on the actions, whilst the derived controller sets it's own [ApiAuthorize] attribute. The attributes are fired in the right order (first ApiAuthorize and then ValidateModel). I have not set the order and I don't know if it's possible.
The ApiAuthorize looks like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
public class ApiAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
//Do some checks...
if (!authorized)
actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, new CustomNotAuthorizedException());
}
}
However the problem is that even although the attribute creates an error response, the action still proceeds further by going into ValidateModel and then into the action itself.
How is it possible to stop the api from processing the request further at the Authorize and ValidateModel levels?
The problem was that the Authorize attribute code was not setting the response. It has to be like this:
if (!authorized)
{
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, new CustomNotAuthorizedException());
actionContent.Response = response;
}
Related
I want to have a Controller Hierarchy assembly at webapi 2 website to handle common CRUD service operation.
I Started with an abstract webapi class like this (I removed unnecesary code and injection for simplicity):
public abstract class EntityController<T> : ApiController {
[Route("GetAll")]
[HttpGet]
public IHttpActionResult GetAll()
{
//code
}
[Route("Add")]
[HttpPost]
public IHttpActionResult Add(T entity)
{
//code
}
[Route("Delete")]
[HttpDelete]
public IHttpActionResult Delete(T entity)
{
//code
}
[Route("Edit")]
[HttpPost]
public IHttpActionResult Edit(T entity)
{
//code
}
}
First, I noticed that if we have controllers in external assemblies, the routes are not resolved properly. I found this post:
https://www.strathweb.com/2012/06/using-controllers-from-an-external-assembly-in-asp-net-web-api/
and when I apply the proposed changes, the routes dont use the "route" attribute. I.E. if I have the following derived class:
[RoutePrefix("api/thebooks")]
public class BookController : EntityController<Book>
{
public BookController() : base()
{
}
}
The controller prefix is api/book instead api/thebooks and if I try to call "Add" I have the exception:
Multiple actions were found that match the request:
Add on type [...]
Edit on type [...]
Find on type [...]
How can I indicate that I will use the Route and RoutePrefix attribute on inherited Controllers?
Thanks in advance!!!
Both Route and RoutePrefix are not inherited because Inherited is set to false as shown below:
[Inherited=false)]
public class RoutePrefixAttribute : Attribute
You can read more about Inherited here.
You will need to decorate your subclasses again unfortunately.
It is possible to override the inherited false of the Route and RoutePrefix with a bit of jiggery pokery.
Firstly inherit from DefaultDirectRouteProvider - notice the inherit true inside GetCustomAttributes
public class InheritanceDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(true);
}
}
Then in you application startup class (WebApiConfig or Startup) register it against your HttpConfiguration as an extension, then change the line in the startup from config.MapHttpAttributeRoutes(); to config.MapInheritedAttributeRoutes();
public static class HttpConfigurationExtensions
{
public static void MapInheritedAttributeRoutes(this HttpConfiguration config)
{
config.MapHttpAttributeRoutes(new InheritanceDirectRouteProvider());
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.MapInheritedAttributeRoutes();
app.UseWebApi(config);
}
}
Taken from this article https://www.strathweb.com/2016/06/inheriting-route-attributes-in-asp-net-web-api/
I am trying to get Web API versioning working with an inherited class. I am working with two very simple variations of the stock Values controller.
[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class ValuesController : ApiController
{
// GET api/values
[Route("")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[Route("{id:int}")]
public virtual string Get(int id)
{
return "value from 1";
}
}
[ApiVersion("2.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class Values2Controller : ValuesController
{
//Want to use the method in the base class
//public IEnumerable<string> Get()
//{
// return new string[] { "value2-1", "value2-2" };
// }
[Route("{id:int}")]
// GET api/values/5
public new string Get(int id)
{
return "value from 2";
}
}
My start up configuration is also pretty straightforward.
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap = {["apiVersion"] = typeof(ApiVersionRouteConstraint)}
};
config.MapHttpAttributeRoutes(constraintResolver);
config.AddApiVersioning(o => { o.AssumeDefaultVersionWhenUnspecified = true; });
}
The non-overridden routes work exactly as I would expect
http://localhost:32623/api/v1.0/Values/12 -> "value from 1"
http://localhost:32623/api/v2.0/Values/12 -> "value from 2"
Calling v1 of the default Get Route
http://localhost:32623/api/v1.0/Values -> Value1, Value2
However trying the same route on the child controller results in an error.
http://localhost:32623/api/v2.0/Values
<Message>
The HTTP resource that matches the request URI 'http://localhost:32623/api/v2.0/Values' does not support the API version '2.0'.
</Message>
<InnerError>
<Message>
No route providing a controller name with API version '2.0' was found to match request URI 'http://localhost:32623/api/v2.0/Values'.
</Message>
</InnerError>
The error message suggests that the overridden member expects a "1.0" route and I am able to work around this with a method like this in the child class.
[Route("")]
public override IEnumerable<string> Get()
{
return base.Get();
}
But this seems less than ideal across a larger application. Is there a way to make this work the way I would like, without these "empty" overrides?
What you need to do is overwrite the DefaultDirectRoutePrivider to allow route inheritance:
public class WebApiCustomDirectRouteProvider : DefaultDirectRouteProvider {
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor) {
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(inherit: true);
}
}
And with that done, you will then need to configure it in your web api configuration along with the custom route constraint
public static void Register(HttpConfiguration config) {
var constraintResolver = new DefaultInlineConstraintResolver() {
ConstraintMap = {["apiVersion"] = typeof(ApiVersionRouteConstraint)}
};
var directRouteProvider = new WebApiCustomDirectRouteProvider();
// Attribute routing. (with inheritance)
config.MapHttpAttributeRoutes(constraintResolver, directRouteProvider);
config.AddApiVersioning(_ => { _.AssumeDefaultVersionWhenUnspecified = true; });
}
So now the inheriting value controller will now have the base routes available in derived controllers
For demonstrative purposes
[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class ValuesController : ApiController {
[HttpGet]
[Route("")] // GET api/v1.0/values
public virtual IHttpActionResult Get() {
return Ok(new string[] { "value1", "value2" });
}
[HttpGet]
[Route("{id:int}")] // GET api/v1.0/values/5
public virtual IHttpActionResult Get(int id) {
return Ok("value from 1");
}
}
[ApiVersion("2.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class Values2Controller : ValuesController {
//Will have inherited GET "api/v2.0/Values" route
// GET api/v2.0/values/5 (Route also inherited from base controller)
public override IHttpActionResult Get(int id) {
return Ok("value from 2");
}
}
You will notice that the route in the child is not used on the overridden action as it too will also be inherited from the base controller.
I am working with a WEB API application in ASP .NET Core 2.0 where I have a custom filter attribute that inherits from ActionFilterAttribute.
How can I access the Model object passed to a controller action in POST call, in ActionFilterAttribute.OnResultExecuted()
The mentioned method is passed a ResultExecutedContext object but I could not find an easy and reliable way to get Model object from it.
The filter I have, looks like the following:
public sealed class MyCustomFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var model = ?????????????
}
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
}
}
and the controller looks like the following:
[Route("api/[controller]")]
public class MyController : Controller
{
[ServiceFilter(typeof(MyCustomFilter))]
[HttpPost]
public async Task<IActionResult> Post([FromBody] List<MyModel> data)
{
// my logic to process model here. In my filter, I want to access data, which is passed into this Post method
return Ok();
}
}
I set a private var in the controller and assign the posted model to it on the controllers action
private YourModel _yourModel;
public ActionResult MyAction(YourModel model)
{
_yourModel = model;
return View();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
//Access _yourModel here
}
To access the model from the filterContext parameter passed in, you can use the below
var model = ((Controller)filterContext.Controller).ViewData.Model;
Try using TempData. Set tempdata to the model in your action.
TempData["Model"] = myModel;
Then access using the TempData on the context.
var x = ((Controller)filterContext.Controller).TempData["Model"];
Hope that's what you meant.
thanks
My controllers have a same action with different authorization filter.
public class RoomAController:Controller
{
[Authorize(Roles = "RoomAEditors")]
public ActionResult SaveNote()
{
...
}
}
public class RoomBController:Controller
{
[Authorize(Roles = "RoomBEditors")]
public ActionResult SaveNote()
{
...
}
}
The code of SaveNote are exactly the same in both controllers. So I want to place it into a base controller like this.
public abastract class BaseController:Controller
{
public ActionResult SaveNote()
{
...
}
}
With this base controller in place, how can I add the action filter in the individual controllers?
public class RoomBController:BaseController
{
[Authorize(Roles = "RoomBEditors")]
// how to add different authorization filters on the SaveNote action?
}
On my abstract base controller, there is a SaveNote action.
You can implement the SaveNote method in your BaseController and override it in RoomA and RoomB controllers using the proper Authorization filter roles.
public abstract class BaseController : Controller
{
virtual public ActionResult SaveNote()
{
throw new NotImplementedException();
}
}
public class RoomAController : BaseController
{
[Authorize(Roles = "RoomAEditors")]
public override ActionResult SaveNote()
{
return base.SaveNote();
}
}
public class RoomBController : BaseController
{
[Authorize(Roles = "RoomBEditors")]
public override ActionResult SaveNote()
{
return base.SaveNote();
}
}
You'll call SaveNote() on the BaseController (Base) inside of each of the SaveNote() actions.
I have 2 controllers Home with
public class HomeController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some irrelevant stuff
base.OnActionExecuting(filterContext);
}
public ActionResult Index()
{
return View();
}
}
and Service with
public ActionResult Confirm()
{ return RedirectToAction("Index", "Home");}
And one ActionFilterAttribute with OnActionExecuting method
public class InvitationModeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some stuff
base.OnActionExecuting(filterContext);
}
}
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new InvitationModeAttribute());
}
}
When I go to localhost/Service/Confirm , OnActionExecuting is fired, but then when RedirectToAction is called, OnActionExecuting is not fired.
How can I catch this after RedirectToAction?
Thanks
Refer this For More clarity
First of all
Remove OnActionExecuting method in controller level
public class HomeController : Controller
{
[InvitationModeAttribute]
public ActionResult Index()
{
return View();
}
}
2nd Controller
public class ServiceController : Controller
{
[InvitationModeAttribute]
public ActionResult Confirm()
{
return RedirectToAction("Index", "Home");
}
}
From MSDN
Scope of Action Filters
In addition to marking individual action methods with an action
filter, you can mark a controller class as a whole with an action
filter. In that case, the filter applies to all action methods of that
controller. Additionally, if your controller derives from another
controller, the base controller might have its own action-filter
attributes. Likewise, if your controller overrides an action method
from a base controller, the method might have its own action-filter
attributes and those it inherits from the overridden action method. To
make it easier to understand how action filters work together, action
methods are grouped into scopes. A scope defines where the attribute
applies, such as whether it marks a class or a method, and whether it
marks a base class or a derived class.