Multiple views for controller based on role in application - c#

In my application exist a little role system. "Admin" and "normal" users. In my application I display data in tables. An admin must have the possibility to create, edit and delete these data, but the "normal" user must not.
So I thought displaying based on the user different Views would be a good idea. But I am struggleing currently with the implementation.
Controller:
public ActionResult Index()
{
if(model.IsAdmin)
{
... // get data
}
else
{
... // get data, but less, or different
}
return (model.IsAdmin) ? this.View("AdminView") : this.View("NormalView");
}
It could look like this.
Is it a good idea to create seperate views on one controller?
Should I work with partial views instead and do the logic inside my view to decide which one I want to display? Doesn't seem right.
What about the folder structure and file naming? Would be nice if they would both match to MyController/Action/Index, instead of .../Index and .../Index2
Thank you and best regards

1.
Most certain this is not a good practice.
You should try to create different Controllers for each Role. Or you could user Areas to point to admin.
If you just load a different view, the guest user could still introduce in the url bar something like "/admin/deleteSmth/1", and do admin actions.
The best it would be if you would implement different controllers or areas.
You should keep the logic far away from views. View are made only to render data. That should be all. All the logic should be made higher than the controller, if you have a business logic layer.
http://www.codeproject.com/Tips/601504/Using-areas-in-ASP-NET-MVC-to-organize-a-project . Areas, use them.

One way of doing this could be
public ActionResult Index()
{
if(model.IsAdmin)
{
... // get data
return view("AdminView")
}
else
{
... // get data, but less, or different
return view("NormalView")
}
//no need to perform extra checks
}

Related

ASP NET MVC Validation Security Issue

I’m starting to develop a ASP NET MVC 3 application and I have sought to follow some good DDD pratices. I have the following situation which would like an opinion.
One of the System features is the creation of activities which one or more users of the system will participate, a meeting for example. Any user with a certain access profile can create new activities, perhaps only the user who create an activity can change her. The question is:
What’s the correct place to insert this rule?
-> In every setter of “Activity” entity? Seems break the DRY.
-> In the repository at the moment of saving the changes? In this case , what would be the correct moment to pass the user permissions?
One more parameter to this method? In the class constructor(In my model, the repositories are interfaces, if I adopt this option,
the dependency would be explicit only in the infrastructure layer where the repositories are implemented?)
-> Controller? It seems to collaborate with an anemic model.
By the way, questions abound... What do you think about?
If you are using ASP.NET Membership you can take advantage of the Roles and Profile providers and use Authorize attributes to restrict access to the actual views where creation or editing occur. For example, the create action could be:
[Authorize(Roles="Activity Admin")]
public ActionResult CreateActivity()
{
return View();
}
Where "Activity Admin" is your creator role. Then your edit could look something like this:
[Authorize(Roles="Activity Admin")]
public ActionResult EditActivity(int id)
{
Activity activity = ActivityRepository.GetActivityByID(id);
if (activity.CreatorID != CurrentUser.ID)
{
return RedirectToAction("Activities");
}
return View(activity);
}
That if statement does the check to make sure the current logged-in user was the user who actually created the activity. The CurrentUser.UserID can be replaced with whatever method you use to retrieve the current logged-in user's unique ID. I usually use the ProfileBase class to implement a class that allows me to track the current user's info. The link below to another SO question shows how you can do that.
How can i use Profilebase class?

State Pattern in ASP.NET MVC 3.0

I have a registration page in my application. It has 3 states and 1 error state(If any error comes):
Fill Basic Information
Select Package
Say Thanks
Error
Now I want to use state pattern here. First I created a console application which is OK. Now I want to implement this logic in my MVC application but I am confused about the structure. I mean how many views, models and controller I need and where to place my logic.
1 controller: RegistrationController
6 action methods:
GET+POST for Index (fill in basic info)
GET+POST for Package
GET for Thank you
GET for Error
This is rough code to make your mind going:
public class RegistrationController : Controller
{
public ActionResult Index()
{
RegistrationState model = RegistrationState.Init();
// just display the "Fill Basic Info" form
return View(model);
}
[HttpPost]
public ActionResult Index(RegistrationState data)
{
// process data and redirect to next step
this.TempData["RegState"] = data;
if (!this.ModelState.IsValid || data.State == State.Error)
{
// error should handle provided state and empty one as well
return RedirectToAction("Error");
}
return RedirectToAction("Package");
}
public ActionResult Package()
{
RegistrationState data = this.TempData["RegState"] as RegistrationState;
if (data == null)
{
return RedirectToAction("Error");
}
// get packages and display them
IList<Package> model = this.repository.GetPackages();
return View(new Tuple.Create(data, model));
}
[HttpPost]
public ActionResult Package(RegistrationState data)
{
// process data blah blah blah
}
// and so on and so forth
....
}
As you can see you still have to write some MVC-related code to act upon state changes. In my example everything's done in action methods. But action filters could be used as well. If you can't come up with a general action filter that can serve many different state objects then it's best to just write the code in action methods.
Another approach
If you know Asp.net MVC good enough you could take this a step further and write a state machine ControllerFactory that would work along with routing in a sense as:
{StateObjectType}/{State}
ControllerFactory would therefore be able to parse view data to a known state object type and pass execution to particular action. According to state. This would make it a specially state machine suited Asp.net MVC application.
The more important question is of course whether you can create the whole application with this pattern or are there just certain parts of it that should work like this. You could of course combine both approaches and provide appropriate routing for each.
Important notices
You should be very careful how you define your error state, because entering invalid field data shouldn't result in error state but rather in data validation errors that actually display within the view beside the field with invalid data (ie. invalid date provided as 13/13/1313). Your error state should only be used for actual object state error that's not related to user input. What would that be is beyond my imagination.
As mentioned in my comment you should check out some Asp.net MVC intro videos and you'll see how validation works in Asp.net MVC. Also rather simple stuff.
State pattern of this kind is not something a regular Asp.net MVC developer would use, because it would most likely complicate code more than taking the normal approach. Analyse before you decide. Asp.net MVC is very clean code wise so adding additional abstraction over it may become confusing. And your domain model (state classes) would most likely have a much more complex code as simple POCOs with data annotations.
In your case data validation would also be more complicated (when used with data annotations) because you object should be validated according to its state which may be different between states. POCO objects are always validated the same. This may mean that we may use more classes but they are smaller, simpler and easier to maintain.
I think you are confusing states. Examples of state are:
Awaiting for a user to register
User registered successfully
User didn't register successfully
Now each of these states would have a page:
localhost:8034/Register
localhost:8034/Register/Success
localhost:8034/Register/Failure
If user can't register because they left some fields empty, they will be in the first state and you will have to display some validation messages.
Because of this, as the minimum I'll have a controller called Register and the following action methods:
Index() GET/POST
Success() GET
Failure() GET

ASP.NET MVC3 - How to serve View() from another controller

So in order accomplish what I asked in this post I did the following:
[iPhone]
[ActionName("Index")]
public ActionResult IndexIPhone()
{
return new Test.Areas.Mobile.Controllers.HomeController().Index();
}
[ActionName("Index")]
public ActionResult Index()
{
return View();
}
Which still serves the same view as the Index action method in this controller. Even though I can see it executing the Test.Areas.Mobile.Controllers.HomeController().Index() action method just fine. What's going on here? And how do I serve the Index view from Mobile area without changing the request URL (as asked in the original post referenced above)?
You have a few options:
Redirect to the Action you'd like to return: return RedirectToAction("Action-I-Want").
Return the View by name: return View("The-View-I-Want").
Note that with the 2nd approach you'd have to put your view in the "Shared" folder for all controllers to be able to find it and return it. This can get messy if you end up putting all your views there.
As a side note: The reason your work doesn't find the view is because default view engine looks for the view in the folder that "belongs" to the current executing controller context, regardless of what code you're calling.
Edit:
It is possible to group all "mobile" views in the same folder. On your Global.asax (or where ever you're setting up your ViewEngine, just add the path to your mobile View in the AreaViewLocationFormats. Mind you, you'll still have to name your views differently.
You can also write your own view engine. I'd do something like detecting the browser and then serving the right file. You could setup a convention like View.aspx, and View.m.aspx.
Anyhow, just take a look at WebFormViewEngine and you'll figure out what works best for you.
The easiest way to send a request to a view handled by another controller is RedirectToAction("View-Name", "Controller-Name").
There are overloads of View() that take route information that might work as well, but they'd require more effort to set up.
Well actually the easiest way is to make one version of your site programmed on standards instead of browser detection :D -- however in direct response to accomplish what it in a more of a ASP.NET mvc fashion, using:
RedirectToAction("ViewName", "ControllerName");
is a good method however I have found it is more practical if you feel you must program for different browser standards to create a primary view and an alternate "mobile" view under your controllers views. Then instead of writing special code on every controller, instead extend the controller like so.
public class ControllerExtended : Controller
{
private bool IsMobile = false;
private void DetectMobileDevices(){ .... }
}
Then modify your controller classes to instead say ControllerExtended classes and just add the one line to the top of each Action that you have alternate views of like so:
public class ApplicationsController : ControllerExtended
{
// GET: /Applications/Index
public ActionResult Index() {
this.DetectMobileDevices();
if(this.IsMobile){
return RedirectToAction("MobileIndex");
} else {
// actual action code goes here
return View();
}
}
}
Alternately you can use return View("ViewName"); but from my experience you want to actually perform different actions as opposed to just showing the result in a different view as in the case of presenting an HTML table as opposed to a Flex table to help iPhone users since there is no flash support in the iPhone, etc. (as of this writing)

ASP.NET MVC2 Access-Control: How to do authorization dynamically?

We're currently rewriting our organizations ASP.NET MVC application which has been written twice already. (Once MVC1, once MVC2). (Thank god it wasn't production ready and too mature back then).
This time, anyhow, it's going to be the real deal because we'll be implementing more and more features as time passes and the testruns with MVC1 and MVC2 showed that we're ready to upscale.
Until now we were using Controller and Action authorization with AuthorizeAttribute's.
But that won't do it any longer because our views are supposed to show different results based on the logged in user.
Use Case: Let's say you're a major of a city and you login to a federal managed software and you can only access and edit the citizens in your city.
Where you are allowed to access those citizens via an entry in a specialized MajorHasRightsForCity table containing a MajorId and a CityId.
What I thought of is something like this:
Public ViewResult Edit(int cityId) {
if(Access.UserCanEditCity(currentUser, cityId) {
var currentCity = Db.Cities.Single(c => c.id == cityId);
Return View(currentCity);
} else {
TempData["ErrorMessage"] = "Yo are not awesome enough to edit that shizzle!"
Return View();
}
The static class Access would do all kinds of checks and return either true or false from it's methods.
This implies that I would need to change and edit all of my controllers every time I change something. (Which would be a pain, because all unit tests would need to be adjusted every time something changes..)
Is doing something like that even allowed?
That's pretty much what I'd do for a decent sized application.
I would return a generic error view return View("Error"); if the user doesn't have any access so you don't need to handle the an error message on every View.
Conceptually, this (the Controller) is where the logic determining what View to return should lie. It stops business logic bleeding into the View.
You could abstract out certain role-dependant parts of the page to partial views to keep the clutter down:
<% if (User.IsInRole("Admin")) { %>
Html.RenderPartial("AdminPanel");
<% } %>
Have you thought about writing your own Action Filter Attribute?
That way you could just decorate your controller with the attribute and save you a lot of copying and pasting, plus if it ever needs to be changed then its only done in one place.
If you get the source code for MVC from Codeplex you can see how the Authorize attribute is done and modify that.

What are the best practices to persist`Conversational state' in ASP.NET MVC applications?

What is the ultimate workaround?)
Desired scenarios are:
Multistep forms.
If a page has tabs on it, the tabs should persist their `viewstate'
Whatever navigation user has chosen, it shouldn't affect (more appropriately, bother) the conversational state management.
Those are just several aspects, but I find them much practically relevant.
I have worked once on Wizard kind of form in Asp.Net MVC and best thing to do in this case is to use Model/ModelBinding to keep track of form input.
We can create a chain of Controller Actions (for each steps) with output model of each serving as the input Model for the next Step (Action).
For example if we have three steps for creating a user. Then UserController can have actions for each steps.
public ActionResult Step1()
{
User user = new User();
return View(user);
}
[Post]
public ActionResult Step1(User user)
{
//perform business validation
RedirectToAction<UserController>(u => Step2(user));
}
After this Step2 action will take over with modified user from Step1 and can render its own view and so on.
You may also want to check out http://blog.maartenballiauw.be/post/2009/10/08/Leveraging-ASPNET-MVC-2-futures-ViewState.aspx. There's an Html.Serialize() helper in MVC Futures. The article refers to it as lightweight Viewstate, but it's effectively just a glorified wrapper around "serialize this object and store the base64 string in a hidden form field." If you need state to be associated with individual pages rather than an entire Session, this helper might be appropriate for your needs.
Not sure about a "workaround", but have you considered using AJAX and jQuery? Both would be appropriate based on the requirements you listed.

Categories