MVC ViewModel Binding Construction vs. Flattening - asp.net-mvc

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.

Related

ASP.NET MVC 4 - Complex Types and Model Binding/Rendering

I've seen a number of similar questions to this, but none seem to have an answer that gets me any further than I am already.
I have a ViewModel that contains some complex types that represent certain stock UI controls and have some validation logic attached. I need to get these to render to a page based on an EditorTemplate/DisplayTemplate and also need the properties to bind properly on submission of the form so that the developer can use the view model within their controllers etc.
Simple example of this is
public class TestViewModel
{
public HierarchyField MyField {get; set; }
}
At it's simplest, I need the developer to be able to create an editor for this view model with a simple
#Html.EditorForModel()
And for any editable parts of the HierarchyField, fields to be set up within a custom EditorTemplate for that type.
I've gotten as far as allowing this to render, by using a custom MetaDataProvider and overriding IsComplexType which, although not something I'm entirely comfortable with, does do the job. Details of that solution are here (http://blogs.msdn.com/b/stuartleeks/archive/2010/04/01/collections-and-asp-net-mvc-templated-helpers-part-3.aspx?Redirected=true)
I'm now stuck trying to get the model binding to update the property on submission of the form and to call a method on the HierarchyField class to perform a little custom validation and update the ModelState based on the result of that call.
I first created a subclass of DefaultModelBinder that had logic within the BindProperty override to check whether the property being bound was of this type. All good so far. Problem comes when I want to call the instance method that takes the intended value and attempts validation of that value. The BindProperty method gives me an instance of PropertyDescriptor which allows me to find the metadata about my property, and a BindingContext with a ValueProvider that allows me to read the value, however, there's not an instance of my complex type returned from:
bindingContext.ValueProvider.GetValue(propertyDescriptor.Name + ".Value")
Where propertyDescriptor.Name + ".Value" equates to the fact that in my EditorTemplate I have the following:
#Html.TextBoxFor(model => model.Value)
Instead of the type, because I've obviously only got a reference to the Value property in the fields that are posted back, I get a String[]. I'm assuming, to get this to work, I will need to register a ValueProvider of some sort for HierarchyField, but ideally I'm looking for something a little more generic, as I have a number of these types to set up, all of which subclass a common abstract type (which all have a property called 'Value' which returns a string representation of their value).
If I'm honest, this whole process seems so painful that I feel like I'm missing something really simple to do this. Unfortunately, I do need this to be generic as there are dozens of these fields, and I do need this model, or something similar for the fields to handle their validation as much of this cannot be done with DataAnnotation validation attributes. I cannot create custom code that's tied to a specific view model and I'd prefer to minimise code that's tied to a specific field, prefering something that applies to the base class (FieldBase) and any of its subclasses.
Happy to post additional code if anyone needs to see it.
I've been using complex types and custom rendering in a few projects without any problems. Maybe I've misunderstood your question but I thought I can post a simplified working example (MVC4) and maybe it'll help you on the way.
In my example the complex type is defined as:
[ComplexType]
public class AccessType
{
public bool ReadAccess { get; set; }
public bool EditAccess { get; set; }
}
The model is defined as:
public class UserAccess
{
[Key]
public Int32 UserId { get; set; }
public AccessType AccessType { get; set; }
}
I've created the following display template in Views\Shared\DisplayTemplates named AccessType.cshtml to handle the rendering of the complex type when it's being displayed.
#model [path to complex type].AccessType
#Html.LabelFor(m => m.EditAccess,"Read access")
#Html.DisplayFor(m => m.ReadAccess)
#Html.LabelFor(m => m.EditAccess, "Edit access")
#Html.DisplayFor(m => m.EditAccess)
I've created the following edit template in Views\Shared\EditorTemplates named AccessType.cshtml to handle the rendering of the complex type when it's being edited.
#model [path to complex type].AccessType
#Html.LabelFor(m => m.ReadAccess,"Read access")
#Html.CheckBoxFor(m => m.ReadAccess)
#Html.LabelFor(m => m.EditAccess, "Edit access")
#Html.CheckBoxFor(m => m.EditAccess)
To the view files for the model using this complex type I've made the following changes:
To the _CreateOrEdit.cshtml file I've added this line to render the complex type part:
#Html.EditorFor(m => m.AccessType)
...to Delete.cshtml and Details.cshtml I've added:
#Html.DisplayFor(m => m.AccessType)
...and to Index.cshtml I've added:
#Html.DisplayFor(_ => item.AccessType)
That was all I needed to render and to use the complex type.
If your problem is more complex then please add some more code to illustrate.

C# object reference from Html to Controller

My domain object Store holds a reference to the object Chain:
public class Store
{
public Chain Chain { get; set; }
}
On creating a new store there is the possibility to set the associated chain. I am realising this by passing the chain objects via ViewBag and use the Html.DropDownListFor for selection:
#Html.DropDownListFor(
x => x.Chain,
new SelectList(ViewBag.Chains, "Id", "Name"))
So far so good, but back in the controller (after submitting) the chain property is null. I figured that I can set the DropDownList to Chain.Id but then I need to load the entity again. Is there a better way to get/keep the correct reference?
Is there a better way to get/keep the correct reference?
No, that's how HTML works. Only the selected value is sent to the server and you need to use this value to load the corresponding entity. Please learn HTML before getting into ASP.NET MVC development. It would help you very much. That's the correct way:
#Html.DropDownListFor(
x => x.Chain.Id,
new SelectList(ViewBag.Chains, "Id", "Name")
)
Well, as a matter of a real fact, to be totally honest with you the most correct way to handle this is to get rid of all ViewBag crap and use a real view model in your application:
#Html.DropDownListFor(
x => x.ChainId,
Model.AvailableChains
)
where AvailableChains will of course be a property of type IEnumerable<SelectListItem> on the view model you prepared for this view. You should always be using a view model and never be passing your domain models to your view if you want to be doing ASP.NET MVC the right way.
And here's how your view model might look like:
public class MyViewModel
{
public int ChainId { get; set; }
public IEnumerable<SelectListItem> AvailableChains { get; set; }
}

Custom Class in Razor View?

Within my Controller I have a class called "ObjectData" that contains an ID and a string:
public class ObjectData
{
public int ObjectId { get; set; }
public string Name { get; set; }
}
I'm trying to pass a List of these to the view via ViewBag, but I don't know how to loop through the items in the array since the classtype isn't normal. I'm doing it this way because I don't want to pass a bunch of Objects and their data to the view, when I only need the ID and Name (is this a valid concern?).
I'm thinking of looping through like this:
foreach (ObjectData i in ViewBag.ParentSetIds)
{
#Html.ActionLink(i.Name, "Detail", new { objectId = i.ObjectId }, null)
}
But Razor doesn't recognize that class type. How can this be accomplished?
You must fully qualify your typename on the line:
foreach (Put.Your.Namespaces.Here.ObjectData i in ViewBag.ParentSetIds)
Razor do not use the same using declaration as your controllers. You may use web.config in the View directory to add such namespaces not to fully qualify it everytime.
Regarding the question if you should be concerned about passing such objects to view. No, there is no need to worry about it. I suggest to move the object ObjectData from controller to the folder next to the controllers folder named ModelView or ViewModel and create the class here. This is something like publicly accepted "hack" to have models which represents just another view on some "real" model. It is same like when you generate MVC3 project it creates for you file AccountModels.cs which contains exactly the same kind of models. But you find it in Model folder, while it may be discussed if it should be rather in ViewModel folder. Also, pass this data as Model not as the part ViewBag if it is not really just helping data.
You could use:
foreach (var i in ViewBag.ParentSetIds)
And let the compiler determine the namespace based on the ViewBag.ParentSetIds
Within my Controller I have a class called "ObjectData" that contains
an ID and a string:
Wait, what? Why do you have a class in your controller?
I'm trying to pass a List of these to the view via ViewBag,
Just use a view model. If you are, you can make List<ObjectData> part of it. In your controller, you load up that list (lets call it ObjectDataList), and send it to your view.
In the view (razor), you'd have something like:
#model MyProject.MyModel
#foreach(var i in Model.ObjectDataList)
{
#Html.ActionLink(i.Name, "Detail", new { objectId = i.ObjectId }, null)
}
Edit:
For clarification, your view model could be:
public class MyModel
{
public string Title {get;set;}
public List<ObjectData> ObjectDataList {get;set;}
}

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.

My custom ASP.NET MVC entity binding: is it a good solution?

Suppose I want to allow to select our entity (from a dropdown, etc) on a page, let's say Product. As a result I may receive this:
public ActionResult SelectedAction(Guid productId)
{
}
But, I want to use model binders power, so instead I write model binder to get my product from repository and instead use
public ActionResult SelectedAction(Product product)
{
if (ModelState.IsValid) {} else {}
}
My model binder will set model state to false if product is invalid.
Now, there're problems with this approach:
It's not always easy to use strongly-typed methods like Html.ActionLink(c => c.SelectedAction(id)) since we need to pass Product, not id.
It's not good to use entities as controller parameters, anyway.
If model state is invalid, and I want to redirect back and show error, I can't preserve selected product! Because bound product is not set and my id is not there. I'd like to do RedirectToAction(c => c.Redisplay(product)) but of course this is not possible.
Now, seems like I'm back to use "Guid productId" as parameter... However, there's one solution that I'd like to present and discuss.
public class EntityViewModel<T> where T : BaseEntity
{
public EntityViewModel(Guid id)
{
this.Id = id;
}
public static implicit operator EntityViewModel<T>(T entity)
{
return new EntityViewModel<T>(entity.Id);
}
public override string ToString()
{
return Id.ToString();
}
public Guid Id { get; set; }
public T Instance { get; set; }
}
Now, if I use
public ActionResult SelectedAction(EntityViewModel<Product> product)
{
if (ModelState.IsValid) {} else {}
}
all the problems are solved:
I can pass EntityViewModel with only Id set if I have only Id.
I don't use entity as parameter. Moreover, I
can use EntityViewModel as property inside another ViewModel.
I can pass EntityViewModel back to RedirectToController and it will keep its Id value, which will be
redisplayed to user along with the validation messages (thanks to MVCContrib and ModelStateToTempData / PassParametersDuringRedirect).
The model binder will get Instance from the repository and will set model state errors like "Not found in database" and so on. And I can use things like ActionLink(c => c.Action(Model.MyProductViewModelProperty)).
The question is, are there any drawbacks here? I can't see anything bad but I'm still new to MVC and may miss some important things. Maybe there're better and approved ways? Maybe this is why everybody uses entity IDs as input parameters and properties?
Overall that looks like a good appoach to me...
As an alternative, you could use POCO for your viewmodel then I think all 3 problems would be solved automatically. Have you seen the Automapper project that allows an Entity to DTO approach? This would give you more flexibility by separating you ViewModel from your EntityModel, but really depends on the complexity of you application you are building.
MVC's ViewDataExtensions might also be useful instead of creating custom containers to hold various viewmodel objects as you mention in number 2.
MVCContrib's ModelStateToTempData should work for any serializable object (must be serializable for any out of process sessionstate providers eg. SQL, Velocity etc.), so you could use that even without wrapping your entity classes couldn't you?

Resources