I'm working on an ASP.NET MVC3 application and I annotated my model with an attribute that specifies what roles can change specific fields for any possible status the model is in. Take this as an example:
public class Model
{
[RoleLimiter(
new[]{Role.Admin, Role.BasicUser, Role.Validator}, // for draft
new[]{Role.Admin, Role.BasicUser, Role.Validator}, // for awaiting validation
new[]{Role.Admin})] // for published etc
public string Subject {get;set;}
}
It looks a bit messy, sure, but it's very easy to change if needed. Now once I have this, it's easy to check for each field the current status and then get the list of roles that can change it. If the current role isn't in it, I'll add a disabled class to the control.
What I wanted to do next is to make a HtmlHelper extension that has the same syntax as the normal EditorFor (or even a straight-forward TextBoxFor), but does this extra check and automatically adds the attribute behind the scenes, but I'm stuck on getting the field info from the expression, ie:
How do you get from
HtmlHelper.TextBoxWithRoleLimitationsFor(x=>x.Subject);
to the attribute attached to x.Subject?
You fetch the LambdaExpression.Body and check whether it's a MemberExpression. You can then get the Member of the MemberExpression and get the custom attributes from that.
Related
I have a model class named Server, it contains many navigation properties and properties, which I want to prevent users from binding it. So I find two approaches of doing so to avoid over-posting attacks.
The first approach is to go to each model class and define an Exclude Bind list , with all the properties and navigating properties that should not be bind by users , as follow:-
[MetadataType(typeof(TMSServer_Validation))]
[Bind(Exclude = "Technology,IT360SiteID, VirtualMachines, TMSServer1,DataCenter,OperatingSystem,Rack,ServerModel,TechnologyBackUpStatu,TechnologyRole,TechnologyStatu ")]
public partial class Server {
}
}
The second approach is to create a view model class , with only the properties that can be modified by users as follow:-
public class ServerViewModel
{
public int ServerSize { get; set; }
[Required]
public String OperatingSystem { get; set; }
public String Commnet { get; set; }
}
I find that the first approach is faster to implement , as I only need to define the Exclude list, while the second approach will require me to create view-model class for each of the domain classes. So which approach is recommended to use and why ?
Thanks
Over-posting occurs due to the default model binder not knowing which fields you actually included in the form.
It will try to map all values in the request to object. Attackers can use your form to add additional fields to
query strings/form post data and add properties as part of the request. The default model binder won't
know the difference. Your Server class will deactivate once the mapping is complete and the update is processed.
To prevent over-posting, set the annotation to include fields in the binding, or create a ViewModel like you mentioned in your code.
So which approach is recommended to use and why ?
Both annotation and ViewModel allow binding only on specified fields, but when you use ViewModel you will not bind against business objects or entities, and you will only have properties available for the input you expected.
Once the model is validated, you can then move values from the input model to the object you used in the next layer.
k. Soctt Allen has a good article about which approach is better, you can take a look at by the following link:
http://odetocode.com/blogs/scott/archive/2012/03/11/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx
It's difficult to tell without seeing the rest of your code, but in general I'd say using the ViewModel is probably a better approach for the following reasons:
You separate your view from your business logic
It is safer. If in the future someone adds a property on Server and forgets the Bind-exclude, you're exposed to over-binding without knowing it. If you use the ViewModel-approach you have to explicity add new properties
Maybe this question is a little bit ambiguous because the answers are going to be based on opinions or something. But I'll try to answer it the best I can and indeed is kind of my opinion. So this is the way I see it:
First approach (Bind attribute): Is faster to implement because you only need to add on your class the name of the property you don't want to expose, but the problems comes when you want your class to exclude some properties for one feature and other properties for another feature, and you can't add fields and sometimes in MVC, the views need more fields that the ones provided by the model class and then you're gonna need to use ViewBag or something else. This approach is very handy for fast and smalls projects, but I still don't like to use ViewBag (For aesthetics reasons)
Second approach (ViewModels): Is more work, and more time but at the end (again in my opinion) you get a cleaner and ordered code and you don't need to use the ViewBag, because you can have the perfect object to send to the view depending on what this View needs, so if you a have an object with different views, again depending on the needs, they could share the same ViewModel or they could have a ViewModel for each one. If you have a solution or a big web project, this approach is going to be very handy to keep an ordered code.
Let me know.
I've been chugging along OK with the use of data annotations made on buddy classes so far.
When it comes to a more complex view that requires a custom view model--one that includes a few select lists, for example...would I need to then transfer my validation attributes to the view model class?
I was planning to pass the full custom view model to populate an "Edit" view, but was hoping to just receive a simple object in my "Save" action.
Where are the flaws in this plan, or is the whole thing just one big pile of fail in the first place?
Thank you.
You're still validating that data that is ultimately going back into the database. So in order to keep your application DRY, you are best off to use the Buddy Classes for the original Model.
Edit
note: this doesn't exactly have anything to do with your question
I personally prefer extend the original Model for anything "Edit" related and I prefer to use a ViewModel for "Display" only (Details pages, List pages, etc).
Example: here's my buddy class, and in it I've added a "RegionName" property that I use in the Edit Page display, but it doesn't have anything to do with the database. You can do a similar thing with custom input data that you want to validate before manipulating it into "database usable" data. I use the RegionID in the database, but I prefer to use the friendly name for the visitor instead of the ID integer
<MetadataType(GetType(UserMetaData))> _
Partial Public Class User
Public RegionName As String
End Class
Public Class UserMetaData
<DisplayName("region name")> _
<Required(ErrorMessage:="region name is required.")> _
Public Property RegionName As String
End Class
Your view model will still inherit the validation from your base model.
Don't know if this helps but I put my validation attributes against my model so that wherever i use the model i get the same validation. not ideal for some projects i realise.
actually, i put the attributes agains a partial class rather than my model because 90% of the time my model comes from a linq 2 sql file in my data repository
my controller then simply checks if the model is valid or not and the view does nothing except display data and errors really.
unsure if this is what you're asking though
Summary: DataAnnotation's automatic handling of an "int?" is making me rethink using them at all.
Maybe I'm missing something and an easy fix but I can't get DataAnnotations to cooperate. I have a public property with my own custom validation attribute:
[MustBeNumeric(ErrorMessage = "Must be a number")]
public int? Weight { get; set; }
The point of the custom validation attribute is do a quick check to see if the input is numeric and display an appropriate error message. The problem is that when DataAnnotations tries to bind a string to the int? is automatically doesn't validate and displays a "The value 'asdf' is not valid for Weight."
For the life of me I can't get DataAnnotations to stop handling that so I can take care of it in my custom attribute.
This seems like it would be a popular scenario (to validate that the input in numeric) and I'm guessing there's an easy solution but I didn't find it anywhere.
Here's a workaround (as I wouldn't really call this a solution). Add a Messages.resx file inside the App_GlobalResources folder of your web application. Add the following resource inside:
Key: PropertyValueInvalid
Value: {0} Must be a number
In the Application_Start method of Global.asax add the following:
DefaultModelBinder.ResourceClassKey = "Messages";
We know that authorization's stuff is a cross cutting concern, and we do anything we could to avoid merge business logic in our views.
But I still not find an elegant way to filter UI components (e.g. widgets, form elements, tables, etc) using the current user roles without contaminate the view with business logic. same applies for model binding.
Example
Form: Product Creation
Fields:
Name
Price
Discount
Roles:
Role Administrator
Is allowed to see and modify the Name field
Is allowed to see and modify the Price field
Is allowed to see and modify the Discount
Role Administrator assistant
Is allowed to see and modify the Name
Is allowed to see and modify the Price
Fields shown in each role are different, also model binding needs to ignore the discount field for 'Administrator assistant' role.
How would you do it?
On way I could think to do this is create your own versions of the input extension methods. For example instead of TextBox you could create TextBoxRoles and define it like this:
public static MvcHtmlString TextBoxRoles(
this HtmlHelper htmlHelper,
string name,
string RolesEdit,
string RolesView
)
Then in code it would look like this:
<%= Html.TextBoxRoles("Price", "Administrator","Administrator,Assistant") %>
Then your implementation of TextBoxRoles would check the roles of the current user via User.IsInRole() to determine what should appear on the page.
Of course you would have to do this for every input extension method you use.
Since you already have both the current user and access to the authorization provider in your controllers this is an ideal responsibility for them. Using a naive implementation you might pass a collection of widgets to your view after you filtered which widgets the current user has access to. In the case of your form field, things might get hairy when you consider client side validation.
The binding part would be the most straight forward of all, having a custom binder for these special cases will do the trick specially well since it will have access to the controller context and you can grab the current user from there and bind the values according to your role definitions.
What about something like LinFu, an AOP framework? If it's crosscutting, then declare it is so and treat it as such.
What I'm trying to do is rather basic, but I might have my facts mixed up. I have a details page that has a custom class as it's Model. The custom class uses 2 custom objects with yet another custom object a property of one of the 2. The details page outputs a fair amount of information, but allows the user to post a comment. When the user clicks the post button, the page gets posted to a Details action that looks something like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Details(VideoDetailModel vidAndComment) { ....}
The only fields on the form that is posted are CommentText and VideoId. Here is what the VideoDetailModel looks like.
public class VideoDetailModel
{
public VideoDetailModel()
{
Video = new VideoDTO();
Comment = new CommentDTO();
}
public VideoDetailModel(VideoDTO vid)
{
Video = vid;
Comment = new CommentDTO();
}
public VideoDTO Video { get; set; }
public CommentDTO Comment { get; set; }
}
VideoDTO has a few properties, but the ones I need are VideoId. CommentDTO's pertinent properties include CommentText (which is posting correctly) and a UserDTO object that contains a userId property. Everything other than the CommentText value is not being posted. I also have the following line on the ascx page, but the model value never gets posted to the controller.
Html.Hidden("Model.Video.VideoId", Model.Video.VideoId);
I'm really not sure what I'm missing here. I suppose if I added more form fields for the properties I need, they would get posted, but I only need 1 form entry field for the CommentText. If I could get the same Model objects value that were sent to the page to post with the page, that would help.
I'll be happy to make any clarifications needed here. I'm just at loss as to what's going on.
UPDATE
Okay, it looks like the solution is rather simple. I think using the RenderPartial in the middle of a form is problematic somehow to how the form gets written in html. I can't really put my finger on why things went bonkers, but if I do my RenderPartials before my form and then begin my form with the text entry field and the hidden VideoId, the default ModelBinder works just fine. I was beginning the form, writing the hidden VideoId, rendering several partial views, create my CommentText field, and then closed the form out. The CommentText field would get bound just fine. The hidden VideoId would not. Maybe I missed a rule somewhere about using RenderPartial.
For completeness, the partial view I was rendering took a Comment object and just wrote out it's CommentText data. Several of these objects would exist for a single Video object. All of this data was in a custom type and passed into the View (the main view) as it's Model. This partial view did not have a form and did not have any data entry fields.
I'd need to see more of your view page code to really give a thorough answer here but for the one snippet you posted:
Html.Hidden("Model.Video.VideoId", Model.Video.VideoId);
Should really be:
Html.Hidden("Video.VideoId", Model.Video.VideoId);
or
Html.Hidden("vidAndComment.Video.VideoId", Model.Video.VideoId);
Either way will work but I tend to prefer the first if your controller action only takes a single parameter. The default model binder will be (in your example) looking for a method parameter named "Model" or failing that will look for a property "Model" on your VideoDetailsModel class. Since neither of those exist, it can't bind "Model.Video.VideoId" to anything.
Is there any Model Binding Security implemented in your mvc site? To explain what I mean, take a look at this page and read the section near the end titled "Model Binding Security". Note that this can be within your code OR in your global.asax file.