MVC3 editor template for multiple types - asp.net-mvc-3

I have a model with some parameters that a User should be able to see but not edit and others they should be able to edit. The same is true of the Author. So, I used [UIHint("Author")] and [UIHint("User")] attributes and wrote a couple editor templates, like so:
#inherits System.Web.Mvc.WebViewPage
#if (ViewBag.RoleId > (int)Role.RoleEnum.Author)
{
#Html.TextBoxFor(m => m, new { disabled = "disabled" })
}
else
{
#Html.TextBoxFor(m => m)
}
This almost does what I want. I'd like to be able to apply these attributes to booleans and get check boxes - like the default EditorFor. I suppose I could make another template and use something like [UIHint("AuthorBool")], but I'm hoping to come up with something better.

Hi Oniel,
You could create separate ViewModels for each type of user and use the data annotation of [ReadOnly]. But then you get into the realms of large amounts of repetition.
Personally I would recommend that you create your own version of each data type and implement standard role based handling using additionalmetadata data annotations to customise. Okay bit of work to begin with but then massively re-usable and highly portable.
Example:
[UIHint("MyCustomTemplateControl")]
[AdditionalMetadata("DenyEditUnlessInRole", "Admin")]
public string MyName { get; set; }
or:
[UIHint("MyCustomTemplateControl")]
[AdditionalMetadata("DenyEditIfInRole", "StandardUser")]
public string MyName { get; set; }
You can perform a code based / database based lookup in a class somewhere else that your datatypes templates query to make a decision on whether a user/role should get read/edit access to this property.
Does this make sense?
As a third option, create an editortemplate for the entire object and only include those fields and field types you are interesting in exposing.
MVC is so flexible - I suppose in the end it depends on how DRY do you want to make your code.
Good luck!
Dan.

Related

How do I bypass the limitations of what MVC-CORE controllers can pass to the view?

From what I've read, I'm supposed to be using ViewModels to populate my views in MVC, rather than the model directly. This should allow me to pass not just the contents of the model, but also other information such as login state, etc. to the view instead of using ViewBag or ViewData. I've followed the tutorials and I've had both a model and a viewmodel successfully sent to the view. The original problem I had was that I needed a paginated view, which is simple to do when passing a model alone, but becomes difficult when passing a viewmodel.
With a model of
public class Instructor {
public string forename { get; set; }
public string surname { get; set; }
}
and a viewmodel of
public class InstructorVM {
public Instructor Instructors { get; set; }
public string LoggedIn { get; set; }
}
I can create a paginated list of the instructors using the pure model Instructor but I can't pass InstructorVM to the view and paginate it as there are other properties that aren't required in the pagination LoggedIn cause issues. If I pass InstructorVM.Instructors to the view, I get the pagination, but don't get the LoggedIn and as this is just the model, I may has well have passed that through directly.
An alternative that was suggested was to convert/expand the viewmodel into a list or somesuch which would produce an object like this that gets passed to the view
instructor.forename = "dave", instructor.surname = "smith", LoggedIn="Hello brian"
instructor.forename = "alan", instructor.surname = "jones", LoggedIn="Hello brian"
instructor.forename = "paul", instructor.surname = "barns", LoggedIn="Hello brian"
where the LoggedIn value is repeated in every row and then retrieved in the row using Model[0].LoggedIn
Obviously, this problem is caused because you can only pass one object back from a method, either Instructor, InstructorVM, List<InstructorVM>, etc.
I'm trying to find out the best option to give me pagination (on part of the returned object) from a viewmodel while not replicating everything else in the viewmodel.
One suggestion was to use a JavaScript framework like React/Angular to break up the page into a more MVVM way of doing things, the problem with that being that despite looking for suggestions and reading 1001 "Best JS framework" lists via Google, they all assume I have already learned all of the frameworks and can thus pick the most suitable one from the options available.
When all I want to do is show a string and a paginated list from a viewmodel on a view. At this point I don't care how, I don't care if I have to learn a JS framework or if I can do it just using MVC core, but can someone tell me how to do this thing I could do quite simply in ASP.NET? If it's "use a JS framework" which one?
Thanks
I'm not exactly sure what the difficulty is here, as pagination and using a view model aren't factors that play on one another. Pagination is all about selecting a subset of items from a data store, which happens entirely in your initial query. For example, whereas you might originally have done something like:
var widgets = db.Widgets.ToList();
Instead you would do something like:
var widgets = db.Widgets.Skip((pageNumber - 1) * itemsPerPage).Take(itemsPerPage).ToList();
Using a view model is just a layer on top of this, where you then just map the queried data, no matter what it is onto instances of your view model:
var widgetViewModels = widgets.Select(w => new WidgetViewModel
{
...
});
If you're using a library like PagedList or similar, this behavior may not be immediately obvious, since the default implementation depends on having access to the queryset (in order to do the skip/take logic for you). However, PagedList, for example has StaticPagedList which allows you to create an IPagedList instance with an existing dataset:
var pagedWidgets = new StaticPagedList<WidgetViewModel>(widgetViewModels, pageNumber, itemsPerPage, totalItems);
There, the only part you'd be missing is totalItems, which is going to require an additional count query on the unfiltered queryset.
If you're using a different library, there should be some sort of similar functionality available. You'll just need to confer with the documentation.

Razor partial view prefix field names

When using Razor to render a form for a complex Model that has sub-models, we'd usually use Partial Views to render the sub-models.
Simple example Model:
public class BlogPost
{
public string Content { get; set; }
public List<Comment> Comments { get; set; }
}
public class Comment
{
public string Content { get; set; }
}
BlogPost.cshtml:
#model BlogPost
#Html.TextAreaFor(x => x.Content)
#for (int i = 0; i < Model.Comments.Count; i++)
{
#Html.Partial('Comment', Model.Comments[i])
}
Comment.cshtml:
#model Comment
#Html.TextAreaFor(x => x.Content)
Now for the issue:
Say we want to send the values of all fields to a controller action that takes BlogPost as a parameter. The fields are going to be posted back to the controller like so:
Content=The+content+of+the+BlogPost&Content=The+first+Comment&Content=The+second+Comment
But what we need to have MVC map them correctly to the BlogPost view model, we need this naming convention:
Content=The+content+of+the+BlogPost&Comments[0].Content=The+first+Comment&Comments[1].Content=The+second+Comment
How can this be achieved in a clean way? We can only think of two ways which both seem to compromise the design:
Either we pass the BlogPost as a model to the partial view such that we can define the text area like so: #Html.TextAreaFor(x => x.Comments[i].Content). But this means we couple the partial view for comments to the parent view model - you could think of a scenario where the same partial view should be used in a different context, which is not possible if the partial view depends on the parent view model. Futhermore, the i would have to be passed to the partial view somehow.
Or we fall back to explicitely defining the name of every single field with strings: #Html.TextArea(ViewBag.Prefix + ".Content").
Is there any way to tell the partial view to apply a certain prefix to all field names?
chiccodoro,
if you create and EditorFor template of type Comment, mvc will handle all of this beautifully for you. However, that will only work well in a scenario where the rows are already present in the DB. Exampel from SO:
Submiting Parent & Children in razor
If you need to create new rows on the fly, then you'll have to use a little bit of trickery to allow the fields to operate as required. I used an article from steven sandersons website which allows you to add collection items at runtime and still retains unobtrusive validation etc. see this SO question and related article ref:
Editing a Variable Length List, ASP.NET MVC 3 Style with Table

A `ViewModel` for each page (`Create.cshtml` and `Edit.cshtml`)?

Questions
There are actually two related questions:
Should I create a ViewModel for each page?
If you do not have problems in creating a single ViewModel class for the two pages (Create.cshtml and Edit.cshtml) how can I validate the ViewModel in different ways (depending on the page that is being used)
Source
ViewModel
public class ProjectViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
}
Edit.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = Model.Name;
}
#Html.EditorForModel()
Create.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = "New Project";
}
#Html.EditorForModel()
ProjectValidator.cs
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
private readonly IProjectService _projectService;
public ProjectValidator(IProjectService projectService)
{
_projectService = projectService;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("required field")
/*The validation should be made only if the page is Create.cshtml. That is, if you are creating a new project.*/
.When(p => p.??) //Problem Here!!
.Must(n => !_projectService.Exist(n)).WithMessage("name already exists");
RuleFor(p => p.Url)
.NotEmpty().WithMessage("required field");
}
}
Note that if the user is editing an existing project, validation of the property name should not be done again.
ProjectController.cs > Edit method
[HttpPost]
public ActionResult Edit(Guid projectID, ProjectViewModel model)
{
var project = _projectService.Repository.Get(projectID);
if (ModelState.IsValid && TryUpdateModel(project))
{
_projectService.Repository.Attach(project);
if (_projectImageWrap.Create(project) && _projectService.Repository.Save() > 0)
return AjaxRedirect("Index");
}
return View(model);
}
Notes
If I create a ViewModel for each page, there is a duplication of code since pages have the same properties.
Add a property on the ViewModel indicating what page it is being displayed does not solve my problem as to instantiate the ViewModel, I use AutoMapper.
To validate the data, I use FluentValidator.
Thank you all for your help!
My understanding is that there isn't a 1:1 correlation between ViewModels and Views. Oftentimes you will have a View that will not require a ViewModel to go alongside with it.
You will want to create a ViewModel if and only if you need a Model absolutely paralleled and tailored to a specific View. This will not be the case 100% of the time.
When the functionality / use case /validation is different between the pages I use different models. If its the exact same besides the presence of an ID or something similar I use the same model, and its also possible to just use the same view if the differences are pretty minor.
Since your validation is different, if I were doing it I would create two different models so that I could use the out of the box DataAnnotations, with your validation though it may not be required. You could also on the edit model have a readonly property for name since its not editable any longer.
For me the same object must have the same validation on every time, in main to ensure the consistence of the object, independently if it was created or edited.
i think that you should create only one validation, and edit your "exists" method to pass to verify if it is a new object or the current object in repository.
Personally, I don't have a problem with 2 view models, especially if (as Paul Tyng suggested) you use a base class for the fields that are common to edit and create scenarios.
However, if you really only want a single view model then you would either need to:
add a flag to the view model and use the When() method in your validator. Note though that this will not generate the appropriate client-side only validation
define a second validator and invoke the appropriate one from the controller (i.e. instead of the "automatic" validation)
Provide another view Edit.cshtml which will allow the user to edit the data for a selected item.
Create another view Query.cshtml which based on the ItemName will allow the users to query the Inventory table.
Perform the calculation for the total profit (numbersold times (saleprice-purchasecost). Display the total profit.
(BONUS) Create another view Sell.cshtml that will indicate the sale of an item. Adding one to NumberSold and subtract one from NumberInventory for the selected record.

validation rules in mvc 3.0

I have one view model which is common for 3 to 4 views in this model I also define validation rules.Now problem is that in one of that view I want to overwrite that view model validation rules for two to three fields.so what I do? I don't want to make new view model for that view.
From an MVC architecture standpoint - this is exactly why you use view models.
You should create separate view models for each case. Use automapper (available for free on codeplex) to copy the values between your view model and your entity.
Don't even consider a different way, inheritance, etc - this is what ViewModels are for.
Three options I can think of:
Make a separate ViewModel using AutoMapper to handle some of the heavy lifting.
Make a subclass having different validation rules.
Make a custom ValidationAttribute which is context sensitive (Either by overriding the IsValid(Object, ValidationContext)method, or relying other context information from static methods/properties.
For instance, this Required validation attribute would be ignored if the request came from a certain URL:
public class CustomRequiredAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
if (HttpContext.Current.Request.Url != "urlwhennotrequired")
return base.IsValid(value);
return true;
}
}
If you do go ahead and use inheritance, then make sure that you inherit from abstract class. I think that as the system grows, you are likely to come across a scenario where your abstract class will have to be modified heavily,therefore If I were you, I'd create more view models, even if the code appears to be repetative. In the long term run you'll benefit because you'll be able to modify parts of your applications with as little side affects as possible.
My recommendation is basically what you don't want: create new model classes, but use inheritance to avoid repeating the properties you want. If you are adamantly opposed to creating separate models, you might look into implementing IValidatableObject and have it inspect other properties before validating the properties that you wish to vary.
EDIT:
I don't disagree with Tuliper's answer, but to flesh out my suggestions, consider a scenario in which you want to save a user's data. From one form, you are creating a user; from another, you are simply updating (this is a bit of a stretch but it's for purposes of illustration). The "create" form might require the name of a person referring the user, while the "update" form might not.
Using inheritance, you could do the following:
public class SaveUserModel
{
public int? UserId { get; set; }
...
}
public class CreateUserModel : SaveUserModel
{
[Required]
public string ReferredByName { get; set; }
}
Using IValidatableObject, you could do it this way:
public class SaveUserModel : IValidatableObject
{
public int? UserId { get; set; }
public string ReferredByName { get; set; }
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// if UserId is null, we are creating a user vs. updating
if (UserId != null && string.IsNullOrWhiteSpace(ReferredBySiteUrl))
yield return new ValidationResult("Please specify the name of the person who referred you.", new[] { "ReferredByName" });
}
}
To reiterate, I am not trying to push my answer. I would be inclined to reuse models if they are exactly the same across different views, but generally there are enough differences to warrant simply creating separate models. In the end, any perceived technical debt alleviated by adhering to DRY in this situation would a bit of a wash; models tend to be easy to maintain.

How to programatically turn on/off a Data Annotation Validation Attribute

So, I am using ASP.NET MVC 3 and Entity Framework 4.1 (code-first).
I have a class like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Range(18, 99)]
public int Age { get; set; }
}
The range validation is fired correctly. But, for example, in some situations I would like to change the range for the Age attribute. Or even turn it off. How could I do it without changing my Model class? Is this possible to made programatically?
You can use the IValidatableObject interface and define custom validation rules.
See my answer at:
Using Data Annotations to make a field required while searching for another in a form mvc 3
Its usually just a matter of implementing the interface and determine when to enforce your rules.
I just realised the solution for this case.
Eg. A user can have an authorization to create a 14 years old person.
Before save the Model, we can invoke DataContext.GetValidationErrors() and infer if the only error validation is that we want to disable, and then set
DataContext.Configuration.ValidateOnSaveEnabled = false;
So, this way we are able to save the model.
Yes, it is possible to inject validators programmatically. Altering existing validators presents separate issues, as some attributes are read-only, so you may have to delete and replace your existing validator.
You can add a class to work on the validators by following my answer to this question.

Resources