ASP.NET MVC: Dynamically rendering controls based on user's access - c#

In my application, I have two roles: Administrators and Users. Administrators can assign Users to allow them to perform specific functions to a specific object.
Since this goes beyond the scope of what the SimpleMembership Roles can provide, I have a static helper method that checks whether a user has access to a specific function:
public static class SecurityCheck
{
public static bool UserHasAccess(int objectId, string functionName)
{
// Decorates the security provider -- gets logged in User ID and calls to a repository to query the database
// ...
}
}
Which I can then use in my views to determine whether or not a specific function should be rendered for that user based on the object's ID:
#foreach (var item in Model.FooData)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Notes)
</td>
<td>
#Html.ActionLink("View Data", "View", new { #id = item.Id })
#if (SecurityCheck.UserHasAccess(item.id, "Edit Data"))
{
#Html.ActionLink("Edit Data", "Edit", new {#id = item.Id})
}
#if (SecurityCheck.UserHasAccess(item.id, "Delete"))
{
#Html.ActionLink("Delete", "Delete", new {#id = item.Id})
}
</td>
</tr>
}
I have to believe there is a better way to do this, since each individual call to the static method involves a separate round-trip to the database, but I am stuck on where the best place would be to put the code. Some thoughts I have considered:
Adding methods to my ViewModels to pass a list of functions to a repository, returning a list of the functions the user can perform for each object. Thinking about this further, I'm not even sure this is possible, as ugly as it would be.
Keep the ViewModel dumb, and have my application layer service fetch the available functions. This would involve adding additional properties to my domain model objects, which I am not crazy about.
Create a separate service that can be called from the controller that can populate the function list to the ViewModel. This would involve having multiple services injected into each controller -- also not crazy about this.
I'm leaning towards #2., but I still feel like I am overlooking what would be a more solid implementation. Has anyone dealt with something similar to this before?

I think each ViewModel "knows" what can be done with it, isn't it? So we can make implicit explicit. The ViewModel can explicitly have properties such as CanEdit, CanDelete, etc.
The UI should not care why some operations are allowed or not, it simply checks these properties in a way:
#if (item.CanEdit)
{
#Html.ActionLink("Edit Data", "Edit", new {#id = item.Id})
}
You can even come up with a helper that takes another boolean as a parameter to decide whether the control should be rendered (or enabled) or not, but it is minor:
#Html.SecureActionLink(item.CanEdit, "Edit Data", "Edit", new {#id = item.Id})
The idea is that it is not the responsibility of the UI to know how to figure out whether something is permitted due to some business rules or not.
But it is definitely UI's responsibility to know how and what to render in one ViewModel is not Editable or another is ReadOnly (different things can have different states).
Also, since we are talking about DDD I would advice against modeling CRUD operations. In the end of the day DDD is about Ubiquitous Language, and "Create, Update, Delete" is hardly a language business really speaks.
So you will end up with more precise and meaningful properties/operations in your models, such as CanAccept (for order screens) or `CanMakeRefund" (for payments).
You resolve/set these properties when you build up your ViewModel and apply security context to it.
Hope it helps.

Maybe you need to use SimpleMembership roles:
Assigning Roles with MVC SimpleMembership
In the standard MVC membership you can just use something like:
Roles.AddUserToRole(model.UserName, "Admin");
And in you View e.g.:
if (ViewContext.HttpContext.User.IsInRole("Admin"))

Related

.NET c# - Customize Display Attribute

.NET MVC Application EF code first, using Identity 2
Each application user belongs to a specific user_type (ApplicationUser.user_type). (the application uses Identity roles too, but they are completely independent of this user_type).
also, I am extensively using Display attribute on properties of models and viewmodels with:
[Display(Name="some literal"]
public string someProperty { get; set; }
and in the view:
#Html.LabelFor(model => model.someProperty)
or
#Html.DisplayNameFor(model => model.someProperty)
But now I am required to, in some cases, display different things if the logged-in user is of a specific user_type ("Client").
What would be the best way to implement this? I am guessing maybe its possible to customize the Display attribute so that it accepts a parameter "userType", so I could decorate the properties with something like:
[Display(Name="This will be shown to users with user_type: Client", UserType="Client"]
[Display(Name="This will be shown to everyone else"]
public int myProperty { get; set; }
or something like that... but I have no idea how to do it... any help is appreciated
To me it seems that you are trying to put too much logic/responsibility in one place.
I recon you would manage to come up with something to deal with this scenario, but, if you do, you'll risk ending up with an inter tangled property which behaviour will depend on all sorts of external parameters. The more you'll add, the more complex it will become. That can be hard to maintain.
I am not fond of "keep it simple" but I think it does apply here, by keeping it simple in maintenance.
IMO you have a couple of options to help you out:
create a complete new view and a model for this client page
add a propery to your (view)model which contains the string.
add the string to the page and handle it with razor.
use a viewbag or similar temp data container
So, to sum it: I dont think expanding the Display attribute would be the way to go here and consider one (or another) of the methods mentioned above.

How do I get a Strongly typed MVC view to post back the instance of the model it's passed?

So, I'm trying to take a view I wrote to create an instance of a model and use it to edit an existing instance from the database.
So, it's pretty simple to just pass in the existing instance by ID
public ActionResult Index(int id)
{
return View(EntityContext.Policies.Where(x => x.ID == id).First());
}
The problem I'm having is that when the form posts:
#using (Html.BeginForm("Index", "Save", FormMethod.Post, new { #id = "form" }))
{
<fieldset>
...
#Html.TextBox("property")
#Html.TextBoxFor("otherProperty")
...
<input id="submit" type="submit" value="Save" />
</fieldset>
}
The controller that it posts to :
public class SaveController : Controller
{
VectorDaemonContext vdContext = new VectorDaemonContext();
//
// POST: /Save/
[HttpPost]
public ActionResult Index(Policy policy, string property, int otherProperty) {
//Stuff to do with the policy
}
}
The controller gets a Policy object, but it only has the fields set in it that I've shown on the view and allowed to be edited
What I'd like to do is have the view post the instance it gets from the original controller. Is this something I can do simply?
The simplest way I know of to do this would be to make a hidden field on the view for the ID of the Policy, then hit the database to pull back the original instance. I'm already having to hit the database more than I'd like to because of some domain specific stuff, so if I can avoid this, I'd like to.
Correction:
The simplest safest way I know of to do this would be to make a hidden field on the view for the ID of the Policy, then hit the database to pull back the original instance.
If you send the record in its entirety to the client, and then allow that client to post it back, you are giving total control over that record to the client even if you don't "allow" them to edit fields. Because a sneaky client can bypass your app and directly post any data they want and alter those fields. So, this is dangerous and insecure to do. Even if you trust your clients, it's just not a good idea.
The safe thing to do is only take the fields you want them to enter, and apply them to a record you retrieve from the database server-side. This way, only the data you allow them to update gets changed.
However, if you're bound and determined, the way to do this is to create hidden fields for all the variables you do not wish to have the users see or edit. If you want them to see the fields, but not edit, you could use textboxes and make the fields read-only (again, the user could bypass this, so it's not any real security). Or just display the data and use a hidden input.
A web browser will only post data that exists in form controls that are no disabled inside a form element. (or data you send in an ajax request). So any data you want posted back to the server must exist in controls.
I would stick it in a hidden field. And yes, you can simply do it.
#Html.HiddenFor(m => m.ID)
I'm just taking a wild guess you have an ID defined in the model. But you can change it to whatever you want.
You have a several options, some of which you already know:
Pass the id, and re-retrieve from the database
Store the object in the model, hide its values in hidden fields, and pass it back whole
Cache the object somehow, either in the session or another available caching mechanism (Storing an object in the session isn't really a good idea).
Because of the HTTP being stateless, your controller doesn't retain information on which model was sent to what client.

Can i pass another model object to be used in Html.BeginForm?

I have two models which is connected. To simplify lets say I have a Post and Comment. When the details page Post, i want to build a form for posting a comment.
I can do that easily with just plain html. But I wish to use the Html.BeginForm.
First I pass a new Comment object to the details page from the controller.
#{
ViewBag.Title = "Details";
Comment newComment = ViewBag.Comment;
}
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
#Html.EditorFor(newCooment => newComment.Comment, new { htmlAttributes = new { #class = "form-control" }
})
But how can i tell the HtmlHelper to use my Comment model? ("newComment => ... " does not work)
So this happens in your controller. You need to have a view model which is a container for all the objects you want to pass to your view. In your view you then take advantage of the #Model property to access this VM.
public class MyController : Controller
{
public ActionResult Index
{
var myViewModel = new MyViewModel
{
Post = post,
Comment = comment // assumed that these have been read from a DB
};
return View(myViewModel);
}
}
The view model:
public class MyViewModel
{
public Post Post {get;set;}
public Comment Comment {get;set;}
}
In your View:
#model some.namespace.to.viewmodel.MyViewModel
#using(Html.BeginForm())
{
}
#Model <-- this is your MyViewModel instance you created in the controller
#Model.Comment <-- this has your Comment in
#Model.Post <-- this has your Post in
In the controller we do this return View(myViewModel). This is telling the Razor engine that we want to set the #Model to our ViewModel and use it in our page.
I tend to stay clear of helper functions that create a whole bunch of html. I like full control over my HTML so I use the #Html.TextBoxFor() or the #Html.TextAreaFor() those "low level" helpers. If I want absolute control then I just write the HTML my self! The id properties should relate to your object levels:
<input id="Post.Name" type="text" />
It's kind of the architecture of MVC right, so you have Models which define the DB Domain. You have Views which show the model information. You have controllers which should delegate getting the models from the DB and sending them to the page. To get multiple Models to the page we need to use a ViewModel.
The semantics are important and widely used through many systems. Web and Desktop, so they are pretty important to understand.
In my products, I use the N-Tier architecture approach. I have services, DALs, Controllers, ViewModels, Models, DTOs.
DTOs are what I map models to, it stands for Domain Transfer Objects. So I got my Domain Model, I may not want everything on it (I don't use navigation properties for example so my DTOs have those navigation properties), in order to reduce it I create a DTO which is what is used around my system to transport that data. When I want to save that data I map it back to the Domain Model and persist to the database.
A key example is if you are using the ASP.NET Identity stuff. So ASP.NET went down the route of making the authentication EF Code First friendly. If you look at the User model for this, it has a tonne of properties and navigation's that I don't need to use. Especially if you just want to register a user.
My registration DTO will have a field my User does not. ConfirmPassword, I want it on my register so that I can confirm that the original password is what they meant it to be. But it stops at my validation layer, past then, it gets completely dropped - when we've confirmed the passwords match I only need the original password they entered.

MVC ViewModel that lives in Session - how to update?

I have done that the "standard" way:
public ActionResult Respondent()
{
return View(Session["Respondent"]); //passing the model
}
[HttpPost]
public ActionResult Respondent(Respondent resp)
{
repository.UpdateRespondent(Respondent resp);
Session["Respondent"] = respondent; //put back into session
return View(respondent); //redraw view, passing in respondent
}
And it works. I am passing the respondent model only for MVC to collect FORM values automatically, in the POST action, where inside the view I have these for all the properties:
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.FirstName)
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
// and so on...
}
My question is - if I am already using a Session object (that lives in Session),
Is there any way I can use the Session object as a Model inside the view, so that HttpPost works including all the validation. How then, the values will get collected and put back into the Session?
Thank you.
If I understand your question, best practices preaching aside, you CAN pass a session object as a Model.
2 Caveats:
Session objects should be cast when passed
return View((RespondentObject)Session["Respondent"])
On the View, remember to bind to object type
#model perseus.Models.RespondentObject
I recommend you read from and write to Session in your Controllers.
You pointed out that you will be using multiple partials to create your form. You have 2 choice:
Create a Model to be received by Action which includes all objects.
Pass every parameter individually to your Action.
You asked why it is bad form to use object:
Because they're untypes
Because Sessions are flaky (unreliable)
That said, you know your app and architecture best. You have to make your decisions and support them. As much as I'm a purist, I believe best practices are a guide not a religion. Circumstances vary.

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.

Categories