How to append a prefix to action name according to a particular route - asp.net

I'm using asp.net mvc 4 and web api. My route is like this:
/api/{controller}/jqGrid/{action}/{id}
for example, if the route is :
/api/User/jqGrid/List
I hope it will route to the action name "jqGrid_List" of the User controller.
How can I achieve this?

hmm, I don't know if it's acceptable to answer my own question. I found out a solution.
First of all, I need to add a JqGridControllerConfiguration attribute to replace the default action selector applied to the controller with my one.
[JqGridControllerConfiguration]
public class UserController : ApiController
{
// GET: /api/User/jqGrid/List
[HttpGet]
public JqGridModel<User> jqGrid_List()
{
JqGridModel<User> result = new JqGridModel<User>();
result.rows = Get();
return result;
}
}
Here's the code of JqGridControllerConfiguration:
public class JqGridControllerConfiguration : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new JqGridActionSelector());
}
}
in JqGridActionSelector, the "action" is modified if a "jqGrid/" exists in the request URL.
public class JqGridActionSelector : ApiControllerActionSelector
{
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
Uri url = controllerContext.Request.RequestUri;
if (url.Segments.Any(s => string.Compare(s, "jqGrid/", true) == 0))
{
controllerContext.RouteData.Values["action"] = "jqGrid_" + controllerContext.RouteData.Values["action"].ToString();
}
return base.SelectAction(controllerContext);
}
}

Not sure why you'd want to do this. But you can still create a "jqGrid_List" action in your User controller and set an ActionName for it, and it'll work.
UserController:
[HttpGet, ActionName("List")]
public string jqGrid_List()
{
return "WORKS";
}
Your Route:
routeTemplate: "api/{controller}/jqGrid/{action}/{id}"

Related

Multiple URLs same action method

I need to share action methods between different controllers. Take for example the following 2 controllers:
public class AController : Controller
{
public ActionResult Index()
{
//print AController - Index
}
public ActionResult Test()
{
//print test
}
}
public class BController : Controller
{
public ActionResult Index()
{
//print BController - Index
}
}
Both controllers have an Index method which is different. The Test method however can be called from both controllers. So I want that when the following urls are entered the Test() method will execute:
AController/Test
BController/Test
I would appreciate any suggestions on how to achieve this.
Assuming the implementation of the Test() action is the same for both controllers, refactor it into a common service:
public interface ITestService {
string Test();
}
public TestService: ITestService {
public string Test() {
// common implementation
return "The test result";
}
}
Then set up Dependency Injection to acquire this service.
Your controllers then can use the common service.
public class AController : Controller {
private readonly ITestService _testService;
public AController(ITestService testservice) {
_testService = testservice;
}
public ActionResult Test() {
var vm = new TestViewModel();
vm.TestResult = _testService.Test();
return View("Test", vm);
}
}
public class BController : Controller {
private readonly ITestService _testService;
public BController(ITestService testservice) {
_testService = testservice;
}
public ActionResult Test() {
var vm = new TestViewModel();
vm.TestResult = _testService.Test();
return View("Test", vm);
}
}
Because the View Test.cshtml is rendered by both controllers, it should be placed in the Views\Shared\ folder.
You can define your own routes as described here: https://learn.microsoft.com/aspnet/core/mvc/controllers/routing
So you can define as many routes as you like to point to the "Test" method inside "AController" just like this:
routes.MapRoute("Atest", "AController/Test",
defaults: new { controller = "AController", action = "Test" });
routes.MapRoute("Btest", "BController/Test",
defaults: new { controller = "AController", action = "Test" });
But you have to define them before the "default" route because otherwise the entered URL will match the default route conditions and so it will enter that route.
It´s also possible to define the route directly in top of the method.
public class AController : Controller
{
[Route("/Some/Route")]
public ActionResult Test()
{
}
}
I want to throw in an alternative solution. Create a base controller class to be inherited by the other two. Whatever you have there will be part of the children.
public class BaseController : Controller
{
public ActionResult Index()
{
//print AController - Index
}
// Add more methods to be shared between the other controllers
}
public class AController : BaseController
{
// Has Index method already from parent
// Unique method for A
public ActionResult Test()
{
//print test 1
}
}
public class BController : BaseController
{
// Has Index method already from parent
// Unique method for B
public ActionResult Test()
{
//print test 2
}
}
This implements the actual functionality in a single place. We use this method for many projects with no issues.

How to rewrite code to use IAuthorizationFilter with dependency injection instead of AuthorizeAttribute with service location in Asp Net Web Api?

I have the custom AuthorizeAttribute where I need to use one of the business layer services to validate some data in the database before giving user a permission to view the resource. In order to be able to allocate this service within the my AuthorizeAttribute I decided to use service location "anti-pattern", this is the code:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
I have couple of other attributes like this in my application. Using service locator is probably not a good approach. After searching the web a little bit I found some people suggesting to use IAuthorizationFilter with dependency injection instead. But I don't know how to write this kind of IAuthorizationFilter. Can you help me writing IAuthorizationFilter that will do the same thing that the AuthorizeAttribute above?
So after struggling for a while I think I managed to resolve this issue. Here are the steps you have to do in order to that:
1) First you have to make GetGroupByIdAttribute passive, and by passive I mean an empty attribute without any logic within it (it will be used strictly for decoration purposes)
public class GetGroupByIdAttribute : Attribute
{
}
2) Then you have to mark a controller method, for which you want to add authorization, with this attribute.
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) In order to write your own IAuthorizationFilter you have to implement its method ExecuteAuthorizationFilterAsync. Here is the full class (I included comments to guide you through the code):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) The last step is to register your filter in the WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now, if needed, I can create additional IActionFilters that use IUserGroupService and then inject this service at the start of the application, from WebApiConfig class, into all filters.
Perhaps try it like shown here:
Add the following public method to your class.
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
Also Add interface IFilterMetadata to your class.
Now when your class is to be created the DI notices that there is a CreateInstance method and will use that rather then the constructor.
Alternatively you can get the interface directly from the DI in your method by calling
context.HttpContext.Features.Get<IUserGroupService>()

Asp.Net MVC 5 custom action routing under an area

i am currently trying to generate this url "/Cloud/Hosting/RoaringPenguin/Manage/Exclusions".
Here is the area registration
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Hosting_default",
"Cloud/Hosting/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
here is the controller
public class RoaringPenguinController : PortalControllerBase
{
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
i have tried added a route onto the action itself like so
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
I have also tried adding some attributes to the controller itself
[RouteArea("Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
but that doesn't seem to work either. If i leave all the attributes off then the final url i get is "/Cloud/Hosting/RoaringPenguin/Exclusions".
Does anyone know how i can get the "Manage" in the url as well?
Just to confirm i have the following set in my RegisterRoutes method under the RouteConfig class
routes.MapMvcAttributeRoutes();
Any help is appreciated. Thanks
Your default area route doesn't allow for the "Manage/Exclusions" part on the end. If you made the URL just /Cloud/Hosting/RoaringPenguin/Exclusions (minus the Manage part of the path) it would work fine.
If you need the route to be exactly that, then attribute routing is your best bet. However, your mentioned attempts at that are all missing something or another. Your controller should be decorated with both RouteArea and RoutePrefix to compose the first part of the path:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : Controller
However, it's typical to actually implement a base controller when dealing with areas, so that you can specify RouteArea in just one place:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
public class HostingBaseController : Controller
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : HostingBaseController
Then, on your action:
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
As you had.
Try with this code
[RouteArea("AreaName", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class SampleController : Controller
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
or
[RoutePrefix("Cloud/Hosting/RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
This will be the first line
routes.MapMvcAttributeRoutes();
After that only you have to write this line
AreaRegistration.RegistrationAllAreas();

ASP.NET Web API custom IHttpControllerSelector for a single route

I need a custom IHttpControllerSelector which should be applied to a specific route only. All other web api routes should use the default implementation of IHttpControllerSelector.
While researching I found the following code that is meant to replace the IHttpControllerSelector at application start, but it replaces the default controller selector completely, which causes that all routes in the application use my custom controller selector:
config.Services.Replace(typeof(IHttpControllerSelector),
new CustomControllerSelector(config));
Is there a way to configure the IHttpControllerSelector for a single route?
You can assign a per-route message handler to the route that needs to use a different controller selection logic. This handler would mark the HttpRequestMessage with a flag that this request needs to be treated differently.
Then simply make the CustomControllerSelector inherit from DefaultHttpControllerSelector and inspect that flag:
if it's set, continue with your custom logic
if it's not set, return to base (DefaultHttpControllerSelector)
Here is the code:
1) message handler, setting the flag
public class RouteSpecificHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties["UseCustomSelector"] = true;
return base.SendAsync(request, cancellationToken);
}
}
2) assigning per route message handler to the specific route only (do not run for other routes)
config.Routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "api/dummy/{id}",
defaults: new {controller = "Dummy", id = RouteParameter.Optional},
constraints: null,
handler: new RouteSpecificHandler { InnerHandler = new HttpControllerDispatcher(config) }
);
3) custom selector respecting the flag:
public class CustomSelector : DefaultHttpControllerSelector
{
public CustomSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("UseCustomSelector") &&
request.Properties["UseCustomSelector"] as bool? == true)
{
//your logic goes here
}
return base.SelectController(request);
}
}
4) registering the selector:
config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config));
edit
If you wish to not inherit from DefaultHttpControllerSelector - then implement IHttpControllerSelector directly, and instead of calling the base.SelectController(request) save the old selector as a field/property in your class
public class CustomSelector : IHttpControllerSelector
{
private HttpConfiguration _config;
public IHttpControllerSelector PreviousSelector {get; set;}
public CustomSelector(HttpConfiguration configuration)
{
_config = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("UseCustomSelector") &&
request.Properties["UseCustomSelector"] as bool? == true)
{
//your logic goes here
}
return PreviousSelector.SelectController(request);
}
}
Then just change the registration:
var previousSelector = config.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config) { PreviousSelector = previousSelector});

ASP.Net MVC3 Controller "globals"

I need to have this code run for each of my actions in one of my controllers. How can I do this without having to copy the code for each of the actions? Is there an init method for the controller?
System.Web.HttpSessionStateBase Sess = HttpContext.Session;
string pid = (Sess["PID"] != null ? Sess["PID"].ToString() : "");
string LogonTicket = (Sess["LogonTicket"] != null ? Sess["LogonTicket"].ToString() : "");
Why don't you wrap this up into properties in a base controller?
class abstract YourControllerBase : Controller
{
public string Pid { get { ... } }
public string LogonTicket { get { ... } }
}
You have a couple options:
Put the code in the constructor
Use OnActionExecuting
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
I think although being abolutely correct, abstract classes and inheritance-based answers make an "asker" feel uncomfortable, since he is trying to work on an even simpler case then inheritace.
A short answer - just put the code into the constructor of the controller.
For example:
public HomeController: Controller {
private string _pid;
private string _logonTicket;
public HomeController() {
System.Web.HttpSessionStateBase Sess = HttpContext.Session;
_pid = (Sess["PID"] != null ? Sess["PID"].ToString() : "");
_logonTicket = (Sess["LogonTicket"] != null ? Sess["LogonTicket"].ToString() : "");
}
//REST OF YOUR CONTROLLER CODE
}
Now you can access _pid and _logonTicket from controller action's code.

Resources