Dynamically building a viewmodel and applying UI validation,Asp.Net MVC 5 - asp.net-mvc

In my application,based on the selection of a field on the UI,I am loading a list of fields to be displayed from the database on the UI.Based on the selection,it is configured in the database each field is required or optional.After the UI is built,I do a post to the same view model(In the controller action).
What is the best way to do this?
I thought about doing this using reflection and add attributes on the properties based on the IsRequired flag against each field in the database,but i guess i have to eliminate the fields not needed from the UI.
So should i create a class as below as the best option or do i have something else?
Public Class ViewModelTicket
{
Public string EmailAddress{get;set}
Public bool IsRequired{get;set}
Public bool ShouldDisplay{get;set}
}
and throw a bunch of if else statements on the View ?
Thanks

I would go with what you started, but I would put it in a collection so that your model is a collection or has a collection of that class you started with. This way, you can easily expand your model to have more fields.
UPDATE
I still think you could use the collection to eliminate the need for ShouldDisplay in your model, and your collection will simply contain fields you want to display or get input for.
Alternatively, you could put your ShouldDisplay value in the class of the containing div.
So you view would have something like this:
<div class="show-#Model.ShouldDisplay">
#Html.LabelFor(m => m.EmailAddress)
#Html.TextBoxFor(m => m.EmailAddress)
</div>
Which would require this css:
.show-false { display: none; }
As for the IsRequired, you could use something like the RequiredIfTrue attribute in your model.
So your model would be:
Public Class ViewModelTicket
{
[RequiredIfTrue(IsRequired)]
Public string EmailAddress{get;set}
Public bool IsRequired{get;set}
Public bool ShouldDisplay{get;set}
}

Related

MVC5 - Dynamically choose which properties are displayed in an form

Let's say I have two users who are accessing a form. One is an admin user and one is a normal user. And only the admin can view and edit AdminOnlyField Then let's say I have the following class:
public class Car
{
public string Make {get;set;}
public string Model {get;set;}
public string AdminOnlyField {get;set;}
}
I'm not able to figure out how to conditionally make the AdminOnlyField visible and editable using #html.BeginForm(), I know you can use properties from ViewBag but in this case all the information I need is coming back from a database so I don't think I can utilize ViewBag.
Is this something MVC can accomplish or do I need to explore Angular?
The easiest way is to insert if(User.IsInRole(stringRole))
Yet if you want to hide this mechanism you can make an EditorTemplate.
The file has to be in location Views/Shared/EditorTemplates/EditFormTemplate.cshtml
For AdminOnlyField you can hide this functionality by template.
#if(User.IsInRole("Admin")
{
<div>
<label>AdminOnlyField: </label>
#Html.EditorFor(model => Model.AdminOnlyField)
</div>
}
Usage:
#Html.EditorForModel("YourCustomTemplateName")
If you need more information:
There is more information

MVC ViewModel Binding Construction vs. Flattening

In my ViewModel (also in my Domain model), I have kinda dynamic Property Structure where the Profile Elements are a List of the base class ProfileVM and refer to a ProfileDefinitionElement (just to explain the ViewModel without pasting the full thing).
public class OwnProfileVM
{
public OwnProfileVM() {}
public ProfileDefinitionVM ProfileDefinitionVM { get; set; }
public ProfileVM ProfileVM { get; set; }
}
So I bind my Properties using a Linq Single statement:
#Model.ProfileDefinitionVM.ProfileElementDefinitions.Single(p => p.Key == ProfileElementKey.CompanyName.ToString()).Title
This works for showing data. But when posting back like this:
#Html.TextBoxFor(model => ((ProfileElementTextVM)model.ProfileVM.ProfileElements
.Single(p=> p.ProfileElementDefinition.Key == ProfileElementKey.CompanyName.ToString()))
.Text
..the model properties are null.
This is because of the parameterless constructor which builds the OwnProfileVM object without any properties filled in.
After some research I found out that there are two ways to solve this:
"Flatten" the ViewModel. So I would have a fixed Property for every Profile Element. This would work, but the disadvantage would be that I couldn't map the data with the Automapper. I would have to fill the ViewModel to the Model "manually". This would result in more Code in the Controller and a "bigger", but simpler ViewModel. Seen in this article
Find a way to pass the Definition data into the ViewModel Constructor to build the list of Properties before posting back.
Now my questions:
Is the second way even possible and if yes, how would this be done? I havent found a way to do this.
If the first question can be answered with yes, which way would you prefer?
Looks complicated. It may be best to simplify it a bit.
In my experience, model properties are null in the controller because the binder cannot understand how to link the form element name with the associated property. For example, I've seen it with lists where foreach has been used:
(model has a) List<Something> Somethings.....
foreach (Something thing in Model.Somethings)
{
#Html.EditorFor(m => thing)
}
This is rendered in the resulting html as <input name="thing"..... which is useless. The solution here is to use a for loop and access the model's properties via their path rather than copying pointers to instances, such as:
for (int i = 0; i < Model.Somethings.Count; i++)
{
#Html.EditorFor(m => Model.Somethings[i])
}
This is then rendered with the correct <input name="Model.Somethings[i]"..... and will be understood by the model binder.
I expect this issue you're facing here is similar. You need to add the necessary accessors to your properties so that the correct names and ids can be rendered in your view and picked up by the binder.
I'm not sure of the exact definition of your class so this example is not likely to be completely right.
This class includes a this[string index] method which will get and set the element using your property key as the index:
public class ProfileElements : List<ProfileElement>
{
public ProfileElement this[string index]
{
get
{
return base.First(p => p.ProfileElementDefinition.Key == index);
}
set
{
base[index] = value;
}
}
}
And in your view, you could use this like:
#Html.TextBoxFor(model => model.ProfileVM.ProfileElements[ProfileElementKey.CompanyName.ToString()].Text)
Hopefully, this will do what you need.

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

mvc.net how to edit member list variables

Given the following model which has a name, url, and an arbitrary list of keywords (I want the user to add a series of keywords) ...
public class Picture
{
public Picture()
{
keywords = new List<string>();
}
public string name {get;set:}
public string url {get;set;}
public List<string> keywords{get;set;}
}
... and the following action in my controller ...
[HttpPost]
public ActionResult Edit(FormCollection fc)
{
if (ModelState.IsValid)
{
// do stuff
}
return View(ModelManager.Picture);
}
In the FormCollection I have the following field
fc["keywords"] = "keyword1,keyword2,keyword3"
And I then create a Picture object based on the form collection.
However, I would prefer to use a strongly-typed action such as
[HttpPost]
public ActionResult Edit(Picture p)
But in this approach, my p.keywords property is always empty. Is there some way to help the framework recreate my p.keywords property before it hits my controller's action method?
I thought an Editor Template might work here, but I don't think there is a way to model bind a nested IEnumerable view model member. Your fastest bet may be handling it directly with FormCollection and some string parsing magic. Otherwise, if you have to strongly-type this, maybe a custom model binder like this could help if you can control your keyword element id's:
public class PictureKeywordBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
Picture picture = new Picture();
//set name, url, other paramaters here
foreach(var item in Request.Form.Keys)
{
if (item.StartsWith("keyword"))
{
picture.keywords.Add(Request.Form[item]);
}
}
//add any errors to model here
return picture;
}
}
Maybe the keyword id's could be setup in a partial view passed the sub model from your parent view:
<% Html.RenderPartial("PictureKeywords", Model.keywords);
Are your keywords seperate text boxes? If so, create an inputs like this and they will be populated by the model binder.
<input name="keywords[0]" type="text">
<input name="keywords[1]" type="text">
<input name="keywords[2]" type="text">
The way I got around this, is to use a hidden input to store the csv string of items, in your case, keywords.
I then hooked into the form submit event (using jQuery) and appended the inputs to form the csv string, which is then stored in the hidden input. This hidden input was strongly typed to a property on my model.
It's a little clunky, but if you have a dynamic number of possible keywords then this works quite well (except if JS is disabled of course)
In what way you are expecting the user to add more keywords? In the form comma separated values(CSV) or by dynamically adding textboxes?
Based on your requirement, i have two solutions with me.

ASP.NET MVC Editing A Collection Best Practices - Your Opinion

Given the following class, what is your opinion on the best way to handle create/edit where Attributes.Count can be any number.
public class Product {
public int Id {get;set;}
public string Name {get;set;}
public IList<Attribute> Attributes {get;set;}
}
public class Attribute {
public string Name {get;set;}
public string Value {get;set;}
}
The user should be able to edit both the Product details (Name) and Attribute details (Name/Value) in the same view, including adding and deleting new attributes.
Handling changes in the model is easy, what's the best way to handle the UI and ActionMethod side of things?
Look at Steve Sanderson’s blog post Editing a variable length list, ASP.NET MVC 2-style.
Controller
Your action method receives your native domain model Product and stays pretty simple:
public ActionResult Edit(Product model)
View
Edit.aspx
<!-- Your Product inputs -->
<!-- ... -->
<!-- Attributes collection edit -->
<% foreach (Attribute attr in Model.Attributes)
{
Html.RenderPartial("AttributeEditRow", attr);
} %>
AttributeEditRow.ascx
Pay your attention to helper extension Html.BeginCollectionItem(string)
<% using(Html.BeginCollectionItem("Attributes")) { %>
<!-- Your Attribute inputs -->
<% } %>
Adding and editing of new attributes is possible too. See the post.
Use the FormCollection and iterate through the key/value pairs. Presumably you can use a naming scheme that will allow you to determine which key/value pairs belong to your attribute set.
[AcceptVerbs( HttpVerb.POST )]
public ActionResult Whatever( FormCollection form )
{
....
}
Use a custom Model Binder, and write the Action methods as you would normally:
ActionResult Edit(
int id,
[ModelBinder(typeof(ProductModelBinder))] Product product
) ...
In your ProductModelBinder, you iterate over the Form Collection values and bind to a Product entity. This keeps the Controller interface intuitive, and can help testing.
class ProductModelBinder : IModelBinder ...
Depends on the experience you are looking to create for the user. I have implemented something similar for tagging content. In the model, Tags are represented as IList, but the UI shows a comma delimited list in a single text field. I then handle merging the items in the list into a string to populate the text field, and I split the input to put items back into the IList in the model.
In my DAL, I then deal with converting the List into LINQ entities, handle inserts and deletes, etc.
It isn't the most straight forward code, but it isn't too difficult to manage and it gives the user an expected interface.
I'm sure there are other ways to handle it but I would focus on what would work best for the user and then work out the mapping details based on that.
Andrew,
I'm thinking something a little more difficult than tags. In this simple case a name / value pair .. color: Red; size: 10; material: cotton.
I think anything that could be used on that could extend to more complex. I.e. Adding a category and adding all its items on the same page. It's relatively easy to add another line using some jQuery, but what's the consensus on sending the info to the ActionMethod?
You can't code:
public ActionResult Whatever(stirng attr1Name, string attr2Name, string attr3Name ...
Also I don't think accepting this would work either:
public ActionResult Whatever(ILIst<Attribute> attributes, string productName ...

Resources