I'm just getting started with Asp.Net Core (and asp.net in general) and I'm trying to build nice controller classes for my rest api.
I'm trying to inherit from a base controller to avoid redefining routes and logic such as validation for resources like so (non working example):
[Route("/api/v1/users/{id}")]
public class UserController: Controller
{
protected int userId;
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
// Validate userId..
userId = (int) RouteData.Values["id"];
}
[HttpGet]
public IActionResult Info()
{
// Use this.userId here
return this.Json("User info..");
}
}
[Route("/friends")]
public class UserFriendsController: UserController
{
[HttpGet]
public IActionResult Info()
{
// Use this.userId here
return this.Json("List of user's friends..");
}
}
I realize I can put this into a single class with multiple actions, but my real scenario involves more controllers that all may want to inherit from UserController.
Route attributes cannot be inherited.
You can play with Routing Middleware. See documentation and the examples on Routing github repo.
Also I can recommend this ASP.NET Core 1.0 - Routing - Under the hood article, as it has good explanation about how routing works:
I'm trying to inherit from a base controller to avoid redefining
routes and logic such as validation for resources
If you want to check whether current user has access right on the resource or not, you should use Resource Based Authorization. For other cross cutting concerns, you can use Filters or Middlewares.
Related
I have the following controller which I wanted to use as an Web API Controller for ajax posts to retrieve data from my user table.
namespace MyProjectName.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly myContext _context;
public UsersController(myContext context)
{
_context = context;
}
[HttpGet]
public List<string> GetInstitutionNamesById(int id)
{
// returns desired list
}
}
}
Now I'd expect the routing of this Function to be like this: /api/users/getinstitutionnamesbyid but apparently it seems to be just /api/users which I find really confusing (what if I add additional HttpGet Functions?).
Can anyone explain me what I am doing wrong? Am I using Web Api Controllers not the Intended way? Is my routing wrong?
Thanks in Advance.
[Route("api/[controller]")]
With this template, you're explicitly stating that you only care about the name of the controller. In your example, GetInstitutionNamesById is the name of the action, which isn't being considered by the template.
There are a few options for achieving what you're asking for here:
Change your [Route] template to include the action name:
[Route("api/[controller]/[action]")]
This option applies to all actions within your controller.
Change the HttpGet constraint attribute to specify the action implicitly:
[HttpGet("[action]")]
This option ensures that the name of your action method will always be used as the route
segment.
Change the HttpGet constraint attribute to specify the action explicitly:
[HttpGet("GetInstitutionNamesById")]
This option allows you to use a route segment that differs from the name of the action method itself.
In terms of whether you're using routing in the correct way here - that's somewhat opinion-based. Generally, you'll see that APIs are attempting to be RESTful, using route templates that match resources, etc. With this approach, you might have something more like the following:
/api/Users/{userId}/InstitutionNames
In this case, you might have a separate InstitutionNames controller or you might bundle it up into the Users controller. There really are many ways to do this, but I won't go into any more on that here as it's a little off-topic and opinion-based.
You just need to name it this way
[HttpGet("[action]/{id}")]
public List<string> GetInstitutionNamesById(int id)
{
// returns desired list
}
and from ajax call /api/users/GetInstitutionNamesById/1
I'm looking for a good place in the ASP.NET Web API lifecycle To update a property in my User entity that is purposed to store the date and time the User last made a request. Obviously, I could just add the code to each of my Controller methods but I would prefer doing this in one place outside of my controllers.
Ideally I would have access to the User principal and could use its Identity property to get the user's ID so that I could retrieve and update my User entity using Entity Framework.
I am currently looking at using a DelegatingHandler implementation.
Can anyone suggest the place in the lifecycle where I should carry this out? A code example would be appreciated.
Create an ActionFilter:
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// Do your work
}
}
Yes, but wouldn't I have to add the ActionFilter to each and every controller method?
No, you can apply it to the controller or to actions.
Alternatively, you can do the following and you will not have to apply it to every controller (sort of like a global filter):
[LogActionFilter ]
public class LogableApiController : ApiController
{
...
}
Then inherit that wherever you want.
And lastly, another option is to add to global filters by finding the App_Start/FilterConfig.cs and add:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogActionFilter());
}
So I have shown you how to apply it to action level, controller level, one or more controllers but not all controllers and then how to apply it to all controllers (global).
I would create an Attribute for your Controller to execute the update on your User Entity with an ActionFilter.
This example explain how to create an attribute for a controller method, it is the same way to do it: Custom Attribute above a controller function
b.e, your controller would be like this:
[SaveUserRequest]
public class HomeController : ApiController
When ASP.NET Core encounters ambiguously named routes, it becomes inert. That is, the application will run without exceptions thrown but, it will fail to process any requests, on any controllers. The calling client receives 500 responses.
I'll show how I got into this mess, and I'd like suggestions of how to fix it.
I have a controller that looks like this:
[Route("api/Subscribers/{id}/[controller]")]
[Route("api/Organizations/{id}/[controller]")]
public class AddressesController : Controller
{
[HttpGet("{aid}", Name = "PostalLink")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
[HttpPost]
[SwaggerResponseRemoveDefaults]
[SwaggerResponse(HttpStatusCode.Created, Type = typeof(PostalRecord))]
public async Task<IActionResult> CreateAddress(Guid id, [FromBody] PostalAddress address)
{
address.ID = Guid.NewGuid();
await createAddress.Handle(address);
return CreatedAtRoute("PostalLink", new { id = id, aid = address.ID });
}
Why the two route prefixes on the controller? Because it fits my microservices (and Swagger documentation) strategy. Nevertheless, in this example ASP.NET Core does not know how to resolve the route name "PostalLink" because it is implicitly bound to the two prefixes:
[Route("api/Subscribers/{id}/[controller]")]
[Route("api/Organizations/{id}/[controller]")]
I can fix the problem simply by changing the HttpGet so that instead of this:
[HttpGet("{aid}", Name = "PostalLink")]
I have this:
[HttpGet("{aid}")] //the route is no longer "named"
Unfortunately, removing the route name is not a real option for me.
What is the prescribed way to fix this?
Below are some of the options I'm considering.
Possibility #1
Theoretically, ASP.NET could simply "figure it out" by itself. For example, if the current request resolved to the route containing the word "Subscribers", then the "PostalLink" name should reference that route. Seen this way, perhaps my code is exposing a bug, defect, or oversight in ASP.NET Core.
Possibility #2
I could collapse my two prefix routes into a single route like this:
[Route("api/{parent}/{id}/[controller]")]
This works, but it undermines my REST documentation strategy. I'm using Swashbuckle to publish endpoint metadata. I want a user of my API to expressly see that my "Addresses" API is serving either "Subscribers" or "Organizations". When I have two explicit route prefixes, the Swagger documentation works correctly (and I properly validate the URI used by the client).
Possibility #3
I could simply override the two prefixes like this:
[HttpGet("~/api/Subscribers/{id}/Addresses/{aid}", Name = "SubscriberLink")]
[HttpGet("~/api/Organizations/{id}/Addresses/{aid}", Name = "OrganizationLink")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
Now my documentation and route validation works, but my implementation is forced to check which route was used to reach the endpoint. That is very doable, but very annoying.
Possibility #4
Perhaps there is a more expressive way to handle this problem without attribute-based-routing? If yes, please share!
Details
My project.json is configured as follows:
"frameworks": {
"dnx46": { }
},
I am using DNX SDK version 1.0.0-rc1-update1. Also, I posted a related SO question for those who would like more context of what I am trying to do.
If your route names are the same for all your actions, why not specify them directly on the controller ?
[Route("api/Subscribers/{id}/[controller]", Name = "SubscriberLink")]
[Route("api/Organizations/{id}/[controller]", Name = "OrganizationLink")]
public class AddressesController : Controller
{
[HttpGet("{aid}")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
}
Have you looked into attribute routing?
E.g. Registering routes with ASP.Net 5's MVC 6 Attribute Routing
Sample from the relevant documentation:
In the following example, app.UseMvc(); is used in the Configure method and no route is passed.
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
public IActionResult Index()
{
return View();
}
[Route("Home/About")]
public IActionResult About()
{
return View();
}
[Route("Home/Contact")]
public IActionResult Contact()
{
return View();
}
}
The HomeController.Index() action will be executed for any of the URL paths /, /Home, or /Home/Index.
I am trying to look for a more clean way to add audit trail function to an exist asp.net MVC and Web Api project which contains hundreds of Controller and ApiController.
The Audit trail log would look like below. Basically I just want to log In what time who did what in this function.
UserID
ActionTime
Controller
Action
Anything I missed ? If there is . Please correct me. Thanks.
Currently I found there are some ways to make it .
Implement an ActionFilterAttribute and write my own log function in the OnActionExecuting, and then decorate all the actions with this attribute.
Implement a base Controller like BaseController for all the exist controller. And write log in the OnActionExecuting. Then change all the controller to inherit from BaseController. (If it is wrong . Please correct me . Thanks.)
For the ApiController. Implement a DelegatingHandler to make it.
For 1 and 2. I need change to all the exist code to make it. like change base class or decorate with new attribute. Considering in my case, This will be a hard work. Because thousands of class or methods need to be changed . I thinks it is kind of verbose. So I wondered if there is some clean way like 3 for ApiController to make it. Thanks.
I find that using global action filters is the best way to handle cross-cutting/aspect-oriented concerns such as this.
Note that this code is not tested.
public class AuditFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var userName = HttpContext.Current.User.Identity.Name;
var time = DateTime.Now.ToString(CultureInfo.InvariantCulture);
var controllerName = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
var actionName = actionContext.ActionDescriptor.ActionName
Logger.Log(string.Format("user: {0}, date: {1}, controller {2}, action {3}", userName, time, controllerName, actionName));
}
}
And somewhere in your application startup pipeline, register the filter globally:
GlobalConfiguration.Configuration.Filters.Add(new AuditFilter());
Are you using a DI container? If you are or want to use a DI container, that could intercept all requests to the controllers. That way you don't change codes in hundreds of controllers, albeit simple change.
Here's Castle Windsor DI.
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel _kernel;
public WindsorControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
public override void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
}
return (IController)_kernel.Resolve(controllerType);
}
}
Have a look at the examples on this site if you intended to use it. I am sure there is a way to use it for both Web API and MVC controllers.
I am using MVC 4 Web API to create a service layer for an application. I am trying to create a global filter that will act on all incoming requests to the API. Now I understand that this has to be configured differently than standard MVC global action filters. But I'm having issues getting any of the examples I'm finding online to work.
The problem I am running into is in registering the filter with Web API.
I have my Global.asax set up like this...
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
MVCConfig.RegisterRoutes(RouteTable.Routes);
MVCConfig.RegisterGlobalFilters(GlobalFilters.Filters);
WebApiConfig.RegisterRoutes(GlobalConfiguration.Configuration);
WebApiConfig.RegisterGlobalFilters(GlobalConfiguration.Configuration.Filters);
}
}
My standard Mvc routing and filters work correctly. As does my WebApi routing. Here is what I have for my webApi filter registration...
public static void RegisterGlobalFilters(System.Web.Http.Filters.HttpFilterCollection filters)
{
filters.Add(new PerformanceTestFilter());
}
And here is the PerformanceTestFilter...
public class PerformanceTestFilter : ActionFilterAttribute
{
private readonly Stopwatch _stopWatch = new Stopwatch();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_stopWatch.Reset();
_stopWatch.Start();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
_stopWatch.Stop();
var executionTime = _stopWatch.ElapsedMilliseconds;
// Do something with the executionTime
}
}
This filter works fine when it is registered with the standard Mvc GlobalFilterCollection, but when I try to register it with System.Web.Http.Filters.HttpFilterCollection I get an error saying that it is not assignable to parameter type System.Web.Http.Filters.IFilter.
So I'm assuming that my PerformanceTestFilter needs to inherit from something other than ActionFilterAttribute in order to be registered as a webapi filter. I'm just not sure what that needs to be.
I imagine I will need to create two individual filters to work with mvc and webapi respectively. If there is a way to create a filter that could be registered to both, that would be great. But my primary concern is simply to get it working for webapi.
Thanks
The following should work. We actually use this for our web API project.
GlobalConfiguration.Configuration.Filters is of type HttpFilterCollection
var filters = System.Web.Http.GlobalConfiguration.Configuration.Filters;
filters.Clear();
filters.Add(new ValidationActionFilterAttribute());
public class ValidationActionFilterAttribute : FilterAttribute, IActionFilter, IFilter
{
...
}
Also, if you're working in a project that contains both MVC and WebAPI assembilies, could you check what's the namespace your
ActionFilterAttribute's namespace. It's fairly confusing cause there
are two ActionFilterAttributes under both:
System.Web.Http.Filters
System.Web.Http.Mvc
Source: Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
It appears that you will need to have two filters, one for API and one for MVC. You can factor the common code into a separate class, and then just use the specific filter to call through to your common class, thus not violating DRY and essentially using the actual filters as wrappers which can be registered as filters.