Currently, we are working on HRIS (Human Resource Information System). We have different user types such as Admin, HR, Employee. But that user types are not static. We want to have different route for each user type.
e.g.
https://website/admin/{controller}/{id}
The route will depend on the user who logged-in in the system. Will read its user type.
May we know if there's a way to configure the route for each user type?
The solution from my point of view would be:
You create a route (URL actually) that matches the pattern:
https://website/admin/3
that holds all the logic for this user - probably, invoking Model, asking for the respective user controller, e.g., superadmin and later on redirecting to...
Another URL matching
https://website/admin/superadmin/3
that now has both controller = superadmin and variable id = 3. It is not said, that the route should be different - you can implement the logic when the controller is either a id (integer) or string to keep the logic more centralized.
Just to mention:
https://website/admin/3
should be fine - you can process the request from the respective Controller without redirecting (see 1. point)
This is very similar to the following question: MVC role-based routing
Essentially you use areas, combined with a routing constraint.
Related
I'm looking at developing an application that will include a CMS. I'm a seasoned web forms developer but only really just moving into MVC.
I have a couple of questions that I hope some of you guys can answer:
First, my current web forms CMS allows users to create a page, and then "drop" any number of user controls onto that page they have created. The way I do this is to create an entry in the DB together with the path and then use the LoadControl method.
I can see I can do this with partial views, but partial views have no code behind. If I've potentially got 100 controls that people can drop onto a page, does this mean that the ViewBag in the controller needs to cater for all 100 controls just in case they are used on the view? For example, a web forms user control will contain logic: rptItems.DataSource = blah; rptItems.DataBind()
With MVC, I'm assuming that logic will be in the view controller and the view would access it by the ViewBag? I'm a little confused at how to do this.
Secondly, how would you handle deep routing?
EG:
Store/Products/Category is fine, but what about Store/Products/Category/Delivery/UK ? Would I need to set up a route in global.asax for each route I need? In web forms, I just called the ReWritePath method and handled the routing myself using regular expressions.
Thanks for the time to read this, and hopefully answer some of my queries
For your second question, (ie, "deep routing"), you can handle this within your controller instead of adding real routes. Each part of the url is available via the RouteData.Values collection inside of your controller action. So, your route may look like
~/Store/Products/Category/{*params}
Assuming typical route configuration, this would call the Category(...) action method on ~/areas/store/controllers/storeController, which could then grap delivery and uk from the RouteData.Values collection.
There are a lot of other approaches to this - storing routes in a database and using associated metadata to find the correct controller and method - but I think this is the simplest. Also, it may be obvious, but if you really only need two parameters beyond 'Category' in your example, you could just use
public ActionResult Category(string category, string region)
{
...
}
and a route:
~/store/{controller}/{action}/{category}/{region}/{*params}
Delivery and UK would be mapped to the the category and region parameters, respectively. Anything beyond uk would still be available via the RouteData.Values collection. This assumes that you don't have more specific routes, like
~/store/{controller}/{action}/{category}/{region}/{foo}/{bar}/{long_url}/{etc}
that would be a better match. ({*params} might conflict with the second route; you'll have to investigate to see if it's a problem.)
For your first question:
You can dynamically generate the view source and return it as a string from the controller, eliminating the need to pass a lot of stuff via ViewBag. If a virtual page from your CMS database requires inclusion of partial views, you would add the references to those components when generating the page. (This may or may not address your problem - if not, please provide more information.)
I'd like to simply check from a Controller whether another URL is authorized.
So for example, I'd like to call into a Controller like so:
[HttpPost]
public ActionResult IsUrlAuthorized(string url)
{
bool isAuthorized = // What do I put here?
return Json(isAuthorized);
}
So I'd like to know what I could call to check on whether the current user is authorized for the passed-in URL or not. I'm guessing the answer has something to do with Routes, which sit a little bit outside MVC?
This is a somewhat similar question but not quite the same thing:
ASP.NET MVC. Check if user is authorized from JavaScript
Since the user may or may not be authorized in general, but may not have the right permissions or role assignments to see a specific URL.
Ideas?
Update: I use standard MVC authorization attributes to lock down my app, so I'll just give an example of what that looks like here. In MVC Routes map to Controllers. A single method on a Controller can be restricted to one or more Roles:
public class HomeController : Controller
{
[Authorize(Roles = "User, Moderator")]
public ActionResult ListRecentPosts()
{
. . .
}
}
Or, an entire Controller can be restricted to one or more roles:
[Authorize(Roles = "Admin")]
public class AdminController : Controller
. . .
The actual URL that any of these controller methods responds to is based on a default mapping in a standard MVC app:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
But, you can be nice to your users and make URLs guessable by adding a lot more Routes - as a result, a Controller method can have many names that point to it. You can't just assume and infer the controller name from the URL (even if it maps out that way for half the URLs in the site).
So presumably I either need a way to ask the Routing engine directly whether a URL is authorized for the current user, or a 2-step of asking the Routing engine for which Controller and Method, then ask if those are authorized - hopefully not by using Reflection and matching Roles directly as that again would appear to assume too much.
Update 2: The way this came up is I have an Account strip at the top of my app. Its state can change by selecting one of several accounts you're authorized as. Depending on where you are in the app, the account you chose might have authorization to view this page - and you might be in the middle of filling out a form you don't want to lose. So the naive approach - just refresh when they pick another account - is harmful, and a waste of the user's time even if there is no form and they're just reading a page that's all text.
While that convenience to the user is nice, the user is going to fairly assume that pages they can't see as a user who shouldn't have permission really are denied (and, it would be harmful to leave them on a page that's forbidden - actions taken from it will fail). So I need to know whether to redirect away based on their new permissions.
One of the things I love about .Net is the way many of its best libraries decompose so well, so you can easily recompose things that are part of its normal functionality, or a new twist. Both the Routing module and MVC appear to be very well constructed, so I have to suspect this can be done.
The cheap hack is to ensure that my authorization module returns a consistent redirect status code when a user isn't authorized, and when the user changes their account in the account strip, fire 2 AJAX calls: One to change account, and then a second to the current page over AJAX just to check the HTTP Status Code. 200 OK means leave the page as is, Redirect means follow the redirect. Obviously this is a little ugly, involves an extra HTTP call, creates a false hit in the logs, and makes an assumption about how authorization is handled across the app.
There could be a secondary concern - the page might be authorized, but just change how it works or looks. This particular app has no change in look based on account (besides the account strip itself), and I can handle functionality changes by just providing a custom event that forms listen to - they can reload any relevant data from the server in response to it.
Using UrlAuthorization.CheckUrlAccessForPrincipal only works if you're only using URL authorization. But for MVC using Routing, we highly recommend that you don't use URL authorization to secure an app.
Instead, we recommend using Authorization attributes on the controller class. The reason is there could be multiple URLs that call the same controller action. It's always better to secure the resource at the the resource and not just at the entry ways.
In this particular case, you'd have to get an instance of the controller given the URL. THat's a little tricky as you'll basically have to run the MVC pipeline from the point where you have the URL to the point where you have the controller. It's possible, but seems heavyweight.
I wonder if there isn't a better and simpler way to accomplish your goals. What is it you're really trying to do?
UPDATE: Based on your scenario, it sounds like this is an initial check just for UI purposes. Perhaps all you need to do is make an asynchronous Ajax request to the URL and check the HTTP Status code. If it's a 401 status code, you know the user is not authorized. That seems like the safest bet.
How about UrlAuthorizationModule.CheckUrlAccessForPrincipal method.
UrlAuthorizationModule.CheckUrlAccessForPrincipal Method (System.Web.Security)
I have a controller that uses the following structure:
.com/Object/375
However, I can also use the following URL when I am accessing special admin rights
.com/Admin/Object/375
I use the same user controls whether you're in the Admin section or not, but they both point to the same Controller Object. I need for the links to maintain that URL structure and not try to kick an Admin user back to the Object controller. I am currently using the route name method, where these are my route names (in global.asax):
"Admin/-Object"
"Object/-Object"
"Object-Object"
These route names catch the following routes:
Admin/Object, Admin/Object/555, Object, Object/323
I then use the following in a route link
Html.RouteLink(id, Request.Url.Segments[1] + "-Object", new { id = id })
This works just fine, but has an odd smell - any other ideas?
To clarify: I need the URL to be properly created based on the current URL structure (with or without the Admin) and the routing will point to the correct controller (the same for both URLs) and the admin specific content will be injected into the page only if in the Admin section (based on URL).
Just to wrap this up, using ViewBag is probably a better idea because using the URL segment might result in unexpected errors, especialy if you move the controls or views around.
I'm developing a website which has a modular structure.
Every segment of the url presents an content item.
For example url: www.mysite.com/blogs/programming/2010/01/
Root item is 'blogs' of type 'area'. It has a child item 'programming' of type 'blog'.
Now there's '2010/01' left of the url.
Last valid (routable) item 'programming' was a blog so I need to map '2010/01' to action
BlogController.Date(int blogid, int year, int? month, int? day)
Every controller comes from a module (separate dll), which registers some item types (blog registers types 'blog' (routable) and 'post' (not routable). 'blog' can have children of type 'post').
When last valid (routable) item of the url is detected, logic knows which assembly and controller to look for. Now I need a way to invoke correct action with correct parameters.
Some routes for item of type 'blog'
{year}/
{year}/{month}
{year}/{month}/{day}
feed/
category/{category-name}/
tag/{tag-name}/
search/{*phrase}
{*post-name}
Any suggestions what would be a simple way to do the routing?
To solve the action parameter signature problem, I personally would create a new Model class "BlogModel" and have only that as your single parameter. This way, you'd have a consistent action parameter signature. However, this would require a bit more work, as you would need to create a custom ModelBinder object "BlogModelBinder" and register it to the ModelBinderFactory (or in MVC3 the DependencyResolver). In the "BlogModelBinder" you simply look up the RouteData's parameters and values and bind it to the corresponding field in your "BlogModel."
From my personal experience, I don't think there's an easy way to register your routes: you still would have to individually register the route urls to a specific action. Unless someone has an efficient way of registering the route urls, you can take solace in knowing that we all have to get our hands dirty with the plumbing code.
My domain model is this: we have a bunch of schools as the root of the "hierarchy". Each school has teachers and courses, and each course has one teacher. I am trying to model this with the logic of the mvc framework and I 'm quite confused. For example, the \school\details\x should give the first page of a school. That should contain a link to a list of its teachers, and a list to each courses.
A list of teachers means that the index action should be parametric to the school the user is looking at: \teacher\id where id is the school. The same with the course list. And then create teacher or course should also be parametric to what school we are looking at:\teacher\create\x where x=school.
How do I carry around the school id? Is there some neat way to do it, or do I need to pass it around all the time, into every view that needs it? It also makes the site URLs very cryptic. I was thinking of a way to make the url structure like {school-alias}\{controller}\{action}\{id}, still I have to find a way to pass around the school. If this is accomplished, then I need to implement some kind of filter that will not allow a user to perform certain actions if the schoolId he is requesting does not match the one in his profile.
I figure that if I 'm carrying the schoolid around the URL, the site is more REST-like, compared to, for example, getting the schoolId from the user's profile.
I would create acronym for every school. For example:
School no. 1 - ABC
School no. 2 - DEF
If i wanted to list teachers, I would write
http://site-address/ABC/teachers/list or just http://site-address/ABC/teachers
To show basic information about school
http://site-address/ABC
The code for routing would be:
routes.MapRoute(
"Default", // Route name
"{acronym}/{controller}/{action}/{id}", // URL with parameters
new {controller = "School", action = "Details", id = ""} // Parameter defaults
);
I would create authorization action filter on teachers,school and classes controller to check if user has access to school defined by acronym parameter in URL. You can check it by comparing filterContext.RouteData.Values["acronym"] with data stored in profile.
Write an extension method to overload rendering of links that extracts the school identifier ( acronym or whatever you choose to use ) from the routing data and adds it to the route values already passed in. This way your action can choose to use the identifier if it is present but is not required to add it to the view data and you do not have to remember to include it in any action links ( you just have to remember to use your action link overload ).
I would make the action link overload quite obviously different so anyone following behind you can see you are doing something unusual. This could be as simple as Html.SchoolActionLink( ...).
For example:
If your url is http://mydomain.com/abc/teachers/list and your route is defined as {school}/{controller}/{action} then the route value dictionary will have the value "abc" at the key "school". The route values can be accessed via HtmlHelper.ViewContext.RouteData.Values.
In the end I 'm answering my own question.
The real solution to this is :Restfull Routing. It implements the functionality in RoR, which is exactly what I need. Too bad this is not a requirement from more people so that it can go into mvc-trunk.