My asp.net MVC app has an area "Company" and an area "Admin".
A company can do CRUD on its users in the company area. I've created a UsersController for this in the Company area.
An admin can do CRUD on a company's users in the Admin area. I've created a CompanyUsersControllers for this in the Admin area.
Both controllers have extremely similar code and I was wondering what the cleanest way is to reuse most of the code.
While writing this question, I figured I could create an abstract UsersControllerBase class with virtual ActionResults. I did this and it works for the Company area. I define attributes on the overriding methods in the UsersController class and call the corresponding abstract method in every overriding method.
Here is an example from the base class:
[UsersControllerBase.cs]
public virtual ActionResult Edit(string slug)
{
var user = UserRepository.GetBySlug(slug);
if (user.CompanyId != CurrentUser.CompanyId)
{
throw new SecurityException(CurrentUser.Id + " attempted to edit a user that does not belong to his company");
}
var model = user.ToViewModel();
AddListsTo(model);
return View(model);
}
And the corresponding override:
[Company/UsersController.cs]
[HttpGet, GET("/company/users/{slug}/edit")]
public override ActionResult Edit(string slug)
{
return base.Edit(slug);
}
The problem is that the Edit in Admin/CompanyUsersController.cs has an extra parameter "companySlug" which is used to find the company for which we are currently editing users.
As you can see in the code above, in Company/Userscontroller.cs we simply derive the company from the CurrentUser.
What would be the best approach to handle this problem?
td;dr
I have 2 controllers with identically named actions that have near-identical method bodies but different parameters. I want to reuse the code as much as possible. pls how do I c#.
If the two methods have different signatures, I don't think it's really worth implementing it as a base class method, though it's not impossible. I would create a protected helper method on the base class and put the shared code in that. Like this (making a few assumptions about your Repository API):
[UsersControllerBase.cs]
protected virtual ActionResult Edit(User user)
{
var model = user.ToViewModel();
AddListsTo(model);
return View(model);
}
[Admin/CompanyUsersController.cs]
[HttpGet, GET("/admin/users/{companySlug}/{slug}/edit")]
public ActionResult Edit(string companySlug, string slug)
{
var user = UserRepository.GetBySlug(companySlug, slug);
return base.Edit(user);
}
[Company/UsersController.cs]
[HttpGet, GET("/company/users/{slug}/edit")]
public ActionResult Edit(string slug)
{
var user = UserRepository.GetBySlug(slug);
if (user.CompanyId != CurrentUser.CompanyId)
{
throw new SecurityException(CurrentUser.Id + " attempted to edit a user that does not belong to his company");
}
return base.Edit(user);
}
If the Edit action in the other controller has an extra parameter, then it shouldn't be an override of the base Edit action, in my opinion. I would create a separate Edit action in the derived controller with two parameters, and make the override Edit action return 404.
[HttpGet]
public override ActionResult Edit(string slug)
{
return HttpNotFound();
}
[HttpGet]
public ActionResult Edit(string slug, string companySlug)
{
// some code...
}
It is not worth implementing a basecontroller because the similar methods have different parameters.
Even if they would have the same signature, the code is cleaner, more readable, understandable and maintainable when focusing on keeping the controllers as lightweight as possible instead of adding so much complexity to save a few duplicate lines of code.
Related
I want to get the below roles(Admin,IT,..) from the database without hard coding on top of the action result. Please provide any help.
[Authorize(Roles = "Admin,IT")]
public ActionResult Index()
{
}
There aren't any super-easy ways to do this. You can apply the [Authorize] attribute to a controller instead of an action, but it is still "hard-coding" it.
You could create a custom Authorization attribute ([link])1, but you would have to store the Routing values in the database, as well as the Roles that were allowed to access the route. However this just shifts the burden of making manual changes into the database from the code.
I don't really think that this should really be considered "Hard Coding" as you have to declare your authorization somewhere, and you can still have different users with different permissions in different environments. Who else but the developer should know best which routes require which authorization? Would you want to break your access control because you changed the routing somewhere?
create an Action finter
public class ValidationPermission : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(System.Web.HttpContext.Current.Session["UserName"] == null)
System.Web.HttpContext.Current.Response.RedirectToRoute("Login");
else{
// code check CheckPermission
}
}
}
Action controller
[ValidationPermission(Action = ActionEnum.Read, Module = CModule)]
public ActionResult Index()
{
// something code
}
You can try with this way
public static Role {
public static string Admin ="Admin";
public static string IT ="IT";
}
[Authorize(Roles = Role.Admin,Role.IT)]
public ActionResult Index()
{
}
I have:
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
And:
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : ApiController
{
//Other methods
}
The strategy used by route rewriting is to get the "Get" method called either "/api/Order/1" or "/api/ManualOrder/1" both pointing to "OrderController", this works.
The problem is when I request any other method in the "ManualOrder" I think it gets lost and can not resolve and returns the exception:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
Project.ProxyAPI.Controllers.ManualOrderController
Projects.ProxyAPI.Controllers.OrderController
Does anyone know how to solve this without duplicating the "Get" method on both controllers?
P.s: This is a hypothetical example.
I'm sure your example is much more complex than the one you present here. But based on the information in your example you could let ManualOrderController inherit from OrderController. I think it makes more sense when you assign the routes. The Route-attributes will not be inherited, so it shouldn't be any problem.
Does anyone know how to solve this without duplicating the "Get"
method on both controllers?
Yes, it will be a duplicate, but it won't contain any logic it will just be a fall-through...
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[HttpGet]
public virtual Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : OrderController
{
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public override Task<HttpResponseMessage> Get(Guid id)
{
return base.Get(id);
}
//Other methods
}
There is a downside to this approach - ManualOrderController will expose methods from OrderController via your default routing table. Depending on what your routing table looks like api/ManualOrder/ExampleOtherNormalMethod may call ExampleOtherNormalMethod on OrderController. This may, or may not, be a desired behavior.
I have users that have one of those roles:
RoleA (Attribute: AuthorizeRoleA)
RoleB (Attribute: AuthorizeRoleB)
In my controller I want to say this:
Everyone that has role of type RoleA
can access all the methods in this controller
[AuthorizeRoleA]
public class HomeController : Controller
{
public ActionResult MethodOne()
{
return View();
}
public ActionResult MethodTwo()
{
return View();
}
//****** Make an exception ********
//So in this case, let RoleA here, but let RoleB too.
[AuthorizeRoleB]
public ActionResult MethodThree()
{
return View();
}
}
And I have another controller:
Everyone that has role of type RoleB
can access all the methods in this controller
Just RoleB! No one else.
[AuthorizeRoleB]
public class AnotherController : Controller
{
public ActionResult Index()
{
return View();
}
}
So, this should be similar with the Authorize attribute when is used to decorate the controller, and the AllowAnonymous when is used inside the same controller, but I don't know how to achieve this behavior with custom attributes(filters).
My goal is to create custom attributes, where I can say:
In AuthorizeRoleA will be included n-roles
and in AuthorizeRoleB will be included n-other roles.
But AuthorizeRoleA will have highest priority than the other attributes.
Note: Maybe this is a duplicate, but I didn't find anything similar to this question.
In my MVC application I have a few different roles: Admin, General User, etc., etc.
I know that I can apply security to my Controllers via the Authorize attribute:
[Authorize(Roles="Admin")]
public ActionResult Create()
{
return View();
}
But I also need to apply some security to the Views to not display certain sections of the View to certain roles:
#if( User.IsInRole("Admin") )
{
#Html.ActionLink("Create", "Create")
}
Is it better to do it the above way, or handle this sort of security in a ViewModel:
public ActionResult Index()
{
var model = new IndexViewModel();
model.CanCreate = User.IsInRole("Admin");
return View(model);
}
View:
#( Model.CanCreate )
{
#Html.ActionLink("Create", "Create")
}
Does the second method have any benefits compared to the first or is it just a preference thing?
The second way is more preferred, as your business logic will stay at model level.
In your example, business logic is very simple. However, imagine that requirements have changed and now not only Admins can create content, but also General Users that signed up more than 1 month ago. With business logic in view you'd have to update all your views.
One way I have done this before is creating an action filter that inherits from the AuthorizeAttribute. The filter can be called something like DisplayIfAuthorizedAttribute, and in addition to the standard AuthorizeAttribute properties, has a property called ViewNameIfNotAuthorized.
The attribute calls the base method to do authorization, and if it fails, returns the ViewNameIfNotAuthorized view. Otherwise, it allows the action method to proceed normally.
You would then render these partial views via action methods, and call the action methods through Html.RenderAction or Html.Action in your parent view. Those action methods would be decorated with the attribute.
You now have a standardized way to do this and no authorization code polluting the internals of your action methods.
This is what the filter would look like:
public class DisplayIfAuthorizedAttribute : System.Web.Mvc.AuthorizeAttribute
{
private string _ViewNameIfNotAuthorized;
public DisplayIfAuthorizedAttribute(string viewNameIfNotAuthorized = null)
{
_ViewNameIfNotAuthorized = viewNameIfNotAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = base.AuthorizeCore(filterContext.HttpContext);
if (!isAuthorized)
{
filterContext.Result = GetFailedResult();
}
}
private ActionResult GetFailedResult()
{
if (!String.IsNullOrEmpty(_ViewNameIfNotAuthorized))
{
return new ViewResult { ViewName = _ViewNameIfNotAuthorized };
}
else
return new EmptyResult();
}
}
Your action method would be decorate as:
[DisplayIfAuthorized("EmptyView", Roles="Admin")]
public ViewResult CreateLink()
{
return View("CreateLink");
}
You may need both...
Note that the 2nd one alone would not be secure, a user might be able to construct the URL for the actionlink in the browsers addressbar. So you absolutely need the attribute for security.
The second one is more a matter of user-friendliness or UI design. Maybe you want the user to be able to click Create and then have a choice to login differently.
Check the authorization in your controller and prepare the Viewmodel for the view according to your role's rules.
The views are used to simply show data. So, imo, they don't have to do roles check etc..
So prepare the ViewModel with the data it should have and let the View only render it. (the boolean property you're using it's enough imo)
I am developing a ASP .Net MVC project but i can't make overload of Index() even when i have defined other method with different no. of parameters & have given proper routing for it . But it doesnot seems to work. So i just want to ask can we make overloaded methods in controller or not?
Controller actions with the same name are possible on the same controller if they are called with different HTTP verbs. Example:
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SomeModel model)
{
return View();
}
When you call GET /Controller/Index the first action will be invoked and when you call POST /Controller/Index the second action will be invoked.
To be more specific, you have to vary it by selection criteria (which might be a verbs change as Darin said, but could also be other selector attributes like NonAction or ActionName). For that matter, you could create your own ActionNameSelectorAttribute derivative to create custom logic indicating when a given method should be used over another.
Update: added code per request.
I am actually creating a sample ActionMethodSelectorAttribute, b/c I couldn't think of a good usecase for just testing on the name that's not already covered by the ActionNameAttribute. Principle is the same either way, though.
public class AllParamsRequiredAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
var paramList = methodInfo.GetParameters().Select(p => p.Name);
foreach (var p in paramList)
if (controllerContext.Controller.ValueProvider.GetValue(controllerContext, p) == null) return false;
return true;
}
}
Basically, this one just gets the names of the params on the action method that it flags, and tests to make sure that the controller's ValueProvider has at least an attempted value of the same name as each. This obviously only works for simple types and doesn't test to make sure the attempted value can cast properly or anyting; it's nowhere close to a production attribute. Just wanted to show that it's easy and really any logic you can return a bool from can be used.
This could be applied, then as follows:
[AllParamsRequired]
public ViewResult Index(int count){/*... your logic ... */}
public ViewResult Index() {/*... more of your logic ... */}
in this example,and default routing, the url mydomain.com/?count=5 will match the first one, and the url mydomain.com/ will match the second one.
Paul