Am I using viewModels correctly (MVC4)? - asp.net-mvc

I have an object that I would like to display in a Details view. The object has a bunch of properties that the view needs.
The object also has parents and grandparents, which I need to display in the view.
What I have for my object viewModel is:
public class ObjectViewModel
{
// Used when creating a new object under a parent object
[HiddenInput(DisplayValue = false)]
public int? ParentObjectId { get; set; }
[Required]
public Object Object { get; set; }
// Info that only the view needs, which is defined in the Controller based on some logic
public string ActiveTitle { get; set; }
// A bre
public IList<Object> ParentObjects { get; set; }
}
I then use this in my Detail controller method:
public ActionResult Detail(int objectId)
{
// TODO: Make this a service call
var object = _db.Objects.FirstOrDefault(s => s.ObjectId == objectId);
if (object == null)
{
return View("Error");
}
var model = new SetViewModel() {
ActiveTitle = object.Name,
Object = object,
ParentObjectId = object.ParentObject.ObjectId,
ParentObjects = _objectService.GetParentObjects(set.ParentObject)
};
return View(model);
}
Does this look right? Or should I be pulling the required fields from the Object model into the viewModel, and not the objects themselves?

To have an object type in your view model is super vague and your code would be hard to support if you are not the original programmer. I would Add the class type to the actual model or use generics to specify the class type as shown below:
public class ObjectViewModel<T>
{
// Used when creating a new object under a parent object
[HiddenInput(DisplayValue = false)]
public int? ParentObjectId { get; set; }
[Required]
public T Object { get; set; }
// Info that only the view needs, which is defined in the Controller based on some logic
public string ActiveTitle { get; set; }
// A bre
public IList<T> ParentObjects { get; set; }
}

Either option will work, and you will often have a mixture of both techniques in a given application.
The key idea is that your view model should contain what the view needs in order to display the data to the user.
If your view merely displays the individual, primitive fields in simply controls, e.g. a series of labels or textbox controls, then your view model should probably specify only the fields, and not the parent object.
However, it's very possible for your view to include a templated or custom control that "knows how" to display a complex object in its entirety. In that case, your view model would need to include the entire object. (In practice I find myself doing this much more often in WPF than ASP-MVC but I've done both).

It looks like the answer will be contextual. Many teams using layered architectures might adopt architectural conventions whereby layers below X should not be referenced directly by your views, and a data access class might be a likely candidate for such a restriction. In your case, it does look like you will be binding your view's structure to the structure directly to your database schema (assuming, since you're using "_db"), which might be considered unreasonably tight coupling.
Also, I'm assuming you're using "object" to represent "any general thing" rather than literally a System.Object, since your objects appear to have an ObjectId property in your lambda expression.

Related

MVC Razor helpers do not render proper ID and Name attributes for fields of interface derived classes

I have a class which looks like this:
public class ApplicationFormModel
{
protected ApplicationFormModel()
{
CurrentStep = ApplicationSteps.PersonalInfo;
PersonalInfoStep = new PersonalInfo();
}
public PersonalInfo PersonalInfoStep { get; set; }
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
where IEducationalBackground, IAboutYou, and IOther are interfaces. I do not use this class directly, but I use derived classes of this one which upon instantiation create the proper instances of EducationalBackgroundStep, AboutYouStep, and OtherStep.
In my view, I am using Razor Helpers such as
#Html.TextBoxFor(model => (model.EducationalBackgroundStep as ApplicationFormModels.EducationalBackgroundAA).University, new {#class = "form-control", type = "text", autocomplete = "off"})
The field 'University', for example, is NOT part of the Interface and I therefore need the cast to access it. Everything is fine for properties of the interface itself, but those which I need to cast for do not end up having the correct ID and Name properties.
For example, instead of EducationalBackgroundStep_University as ID, I only get University. This causes the form to not include this value when submitting it.
I did not have this issue before when I used a base class instead of an interface, but then I had to include the EducationalBackgroundStep, AboutYouStep, and OtherStep in each derived class (and have it then of the correct derived type), but that is what I wanted to avoid.
Is there any way around this? Thank you very much!
The issue with the ID generation is because you are using casting (x as y) and the TextBoxFor expression handler can't determine what the original model property was (more to the point, it doesn't make sense to use the original model property as you're not using it any more, you're using the cast property)
Example fiddle: https://dotnetfiddle.net/jQOSZA
public class c1
{
public c2 c2 { get; set; }
}
public class c2
{
public string Name { get; set; }
}
public ActionResult View(string page, bool pre = false)
{
var model = new c1 { c2 = new c2 { Name = "xx" } };
return View(model);
}
View
#model HomeController.c1
#Html.TextBoxFor(x=>Model.c2.Name)
#Html.TextBoxFor(x=>(Model.c2 as HomeController.c2).Name)
The first textboxfor has ID c2_Name while the second has just Name
You have two options:
1) use concrete classes rather than interfaces for your viewmodel
2) don't use TextBoxFor and instead use TextBox and specify the ID manually (but then you'll lose refactoring)
#Html.TextBox("c2_Name", (Model.c2 as HomeController.c2).Name)
This will give you the ID you're expecting, but as #StephenMuecke rightly points out, this might not bind correctly when you do the POST - so you may still be stuck... but at least it answers the question.
#freedomn-m explained to me why my code wouldn't work and he put me on the right track to find a solution, so he gets the accepted answer.
The workaround I used is the following - so I now have the following classes:
public class ApplicationFormViewModel {
public PersonalInfo PersonalInfoStep { get; set; }
// constructors which take the other classes and
// initialize these fields in an appropriate manner
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
// in our case, XX can be one of 3 values, so we have 3 classes
public class ApplicationFormXX {
public PersonalInfo PersonalInfoStep { get; set; }
// constructor which take the ApplicationFormViewModel and
// initialize these fields in an appropriate manner
public EducationalBackgroundXX EducationalBackgroundStep { get; set; }
public AboutYouXX AboutYouStep { get; set; }
public OtherXX OtherStep { get; set; }
}
To the main View I send the ApplicationFormViewModel and for each of the fields, I call a separate Partial View.
The Partial views render the common fields which are present in the Interfaces and then, depending on the type of the object held by the interface, it calls a different partial view which accepts the correct Model.
Example:
In the main View I have (NOTE: The actions return a partial view):
#model Applications.Models.ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#Html.Action("RenderEducationalBackgroundStep", "ApplicationFormsLogic", routeValues: new {model = Model})
In the Partial View of for the EducationalBackgroundStep, I have:
#model ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#{
var educationalBackgroundType = Model.EducationalBackgroundStep.GetType();
if (educationalBackgroundType == typeof(EducationalBackgroundXX))
{
<text>#Html.Partial("~\\Views\\Partials\\ApplicationForm\\Partials\\ApplicationSteps\\EducationalBackground\\_EducationalBackgroundXX.cshtml", new ApplicationFormModels.ApplicationFormModelXX { EducationalBackgroundStep = Model.EducationalBackgroundStep as EducationalBackgroundXX })</text>
}
// OTHER ELSE IF CASES
}
And then, the _EducationalBackgroundXX.cshtml partial view expects a model like this:
#model ApplicationFormModels.ApplicationFormModelXX
This way, no casting is required and everything works fine with the ModelBinder. Again, thank you #freedomn-m for setting me on the right track.
NOTE: In practice I need more fields than the ones presented here (for navigation and some custom logic), so actually all of these classes inherit an abstract base class (this makes it redundant to have the PersonalInfoStep declared in each of the classes, for example, because it can be inherited from the abstract base class). But for the intents and purposes of this method, what's present here suffices.

MVC binding triggering validations errors

I'm trying to get my head around why (data annotation) validation errors are triggering when the page first loads, prior to any Submit/Posts. But more importantly how to fix this.
Reading SO and the interwebs, the reason seems to be model binding triggers validation errors for the view model properties, prior to the view model properties having values. I'm not sure if this is true and what is actually happening, but it sounds legit.
And I've read two workarounds, which sound a bit hacky:
1. Use the ModelState.Clear in the controller action method on the inital page load, OR
2. Initialiase the view model properties in an empty view model constructor. (Yet to confirm this technique)
Both these techniques sound like work-arounds. I'd rather understand what is happening and design my code appropriately.
Have others come across this issue? And if so, what are you doing?
Code as requested. Below you can see the Data Annotation validation on Property1 which is being triggered on initial requests, i.e. first page load.
Controller action method (refactored for simplicity):
public ActionResult Index([Bind(Include = Property1, Property2, Property3, vmclickedSearchButton)] IndexVM vm, string Submit)
{
bool searchButtonClicked = (Submit == "Search") ? true : false;
if (searchButtonClicked)
{
PopulateUIData(vm); // Fetch data from database and pass them to VM
if (ModelState.IsValid)
{
vm.clickedSearchButton = true; // Used in the vm to avoid logic execution duing initial requests
DoWork(vm);
}
}
return View(vm);
}
// Inital request
IndexVM newVM = new IndexVM();
PopulateUIData(newVM); // Fetch data from database and pass to VM
return View(newVM);
}
Design note:
Ideally I would like to sepate the rendering and submiting logic into separate action methods.
I.e. rendering within a [HttpGet]Index() action method, and submitting within a [HttpPost]Index() action method.
But since I'm using ForMethod.Get in the View as this method is used for searching functionality, I can only use a [HttpGet] Index action method.
View Model (refactored for simplicity):
public class IndexVM
{
// DropDownLists
public IEnumerable<SelectListItem> DDLForProperty1 { get; set; }
public IEnumerable<SelectListItem> DDLForProperty2 { get; set; }
public IEnumerable<SelectListItem> DDLForProperty3 { get; set; }
[Required]
public int? Property1 { get; set; }
public int? Property2 { get; set; }
public int? Property3 { get; set; }
public bool vmclickedSearchButton { get; set; }
}
Note:
The view model is very simple. It contains drop down lists, selected properties for the DDLs, and a validation rule on one of the properties.
Adding a constructor to the view model and initialising the property workaround:
public IndexVM()
{
this.Property1 = 0;
}
The problem is that you are sending an invalid model to your view.
The Property1 in your model is being required and is a nullable int. This does not explain why validation executes, but why the model is invalid.
Your action method is executing the validation during initial load. Model binding will execute validation regardless of http method (get or post).
Since you are requiring Property1 (int?) to NOT be null, by definition your model becomes invalid when it is instantiated. There are several ways to handle this (not sure which is most appropriate though)
Create separate methods for HttpGet and HttpPost in your controller. Do not implement binding for HttpGet.
Use a default value (as you have done already)
Modify the model so that Property1 is not nullable (i.e. int).

Display template not used for interface

I'm having a problem with display templates and dealing with interfaces and objects which implement the interface. In the example I have many objects, which I want to be rendered in a fixed way, I decided to create an interface and reference this in the view which I've decided to put into the shared display templates folder. DisplayFor doesn't seam to work for objects passed to it which implement the interface in the view, does any one know a solution to this.
Its probably easier to explain via code so I've wrote a quick example. The base interface and two classes which inherit from it:
public interface IPet
{
String Name { get; }
}
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
The example display template in shared display templates
#model IPet
<div>#Model.Name</div>
The example view model to be passed to the view
public class VM
{
public IPet Master { get; set; }
public IEnumerable<IPet> Minions { get; set; }
}
The controller (in this case to create mock information)
public ActionResult Index()
{
var viewModel = new VM();
viewModel.Master = new Cat(){Name = "Fluffy"};
var minions = new List<IPet>();
minions.Add(new Dog(){Name = "Dave"});
minions.Add(new Dog(){Name = "Pete"});
minions.Add(new Cat(){Name = "Alice"});
viewModel.Minions = minions;
return View(viewModel);
}
and finally the view which I would expect DisplayFor to work
#model ViewInheritance.Models.VM
<h2>Master</h2>
#Html.DisplayFor(x => x.Master)
<h2>Minions</h2>
#Html.DisplayFor(x => x.Minions)
Given that all the objects are are defined in the view model as the interfaces, howcome it fails to use the display template?
One solution I have found is to simply use the code
#Html.DisplayFor(x => x.Master, "IPet")
To recap, the question is:
Why does this happen?
Is there a way to make DisplayFor correctly work out that a type of Cat which implements IPet should in fact be looking at the common shared view IPet.cshtml?
Thanks
Starting a new MVC application and fixing the code to actually compile the view renders fine. It also renders fine when moving the view into the shared folder.
I Added setter to IPet:
public interface IPet
{
String Name { get; set; }
}
I updated implementation and added public accessors:
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
I left your VM alone and also did not change any code in your View.
Pressing F5, running the MVC application rendered the results as expected (See image).
Unfortunately, I don't think ASP.NET MVC currently supports automatically selecting templates based on implemented interfaces. I think this makes sense because a class could implement multiple interfaces, so if you had templates for more than one of those interfaces, which one should the framework choose?
You could use a base class instead of an interface if your design can cope with it:
Change IPet to a (possibly abstract) class.
Change IPet.cshtml to Pet.cshtml.
Otherwise I think you'll just need to explicitly tell the framework which template to use. Here are some options:
Decorate the view model properties with [UIHint].
Specify the template in your calls to your HtmlHelper methods such as DisplayFor.
Make your own ModelMetadataProvider and change the TemplateHint property of the resulting ModelMetadata.

ASP.NET MVC - Partially updating model from view

I just wondered how people were approaching this situation. It's something that seems like a weak point in my usage of MVC with ORMs (NHibernate in this case)...
Say you have a fine-grained and complicated entity in your model. You will likely have an admin page to manage objects of this type. If the entity is complicated, it is unlikely that you will be modifying the whole entity in one form. You still need to pass the relevant properties to the view, and incorporate changes to those properties in the model when the view returns them.
What does anyone do in this situation?
Create a view model which is (or contains) a subset of the entities properties. Pass this to and from the view. In 'edit' action method in controller, get the object from repository, go though all the properies in the ViewModel and apply them to the Model object (model.a = viewmodel.a, modelb = viewmodel.b). This seems the obvious sensible route, but generates a lot of tedious plumbing code. Also this complicates validation a bit.
Something else?
I've looked briefly at automapper - but this doesn't seem to fit the bill exactly, maybe I'm wrong?
Thanks.
This sounds like the perfect scenario for automapper. You create a view model class which contains a subset of the fields or your real model, and you let AutoMapper take care extraccting values from the domain model object into your view model object. What issues are you having with this approach?
Consider this example:
Here is your domain model and your view model
public class Person
{
public string FirstName
{ get; set; }
public string LastName
{ get; set; }
public string HomeNumber
{ get; set; }
public string Address1
{ get; set; }
public string Address2
{ get; set; }
}
public class PersonViewModel
{
public string FirstName
{ get; set; }
public string LastName
{ get; set; }
public string HomeNumber
{ get; set; }
}
Here is your mapping, you have to create a mapping in both directions from dm->vm and vm->dm.
From what I've seen when using Automapper is that if you map from object A to B and B has a property which A doesn't have, it will be reset. So when I create the map I direct it to ignore those missing properties. I'm not a Automapper expert so I may be using it wrong.
Mapping
Mapper.CreateMap<Person, PersonViewModel>();
// Automapper will reset values in dest which don't exist in source, so be sure to ignore them!
Mapper.CreateMap<PersonViewModel, Person>()
.ForMember(dest => dest.HomeNumber, opt => opt.Ignore());
Finally usage:
Person p = new Person()
{
FirstName = "First",
LastName = "Last",
Address1 = "add 1",
Address2 = "add 2"
};
PersonViewModel pvm = Mapper.Map<Person, PersonViewModel>(p);
// Map to a new person
Person p2 = Mapper.Map<PersonViewModel, Person>(pvm);
// Map to the existing person just to update it
Person p3 = new Person()
{
HomeNumber = "numberHere"
};
// This will update p3
Mapper.Map<PersonViewModel, Person>(pvm, p3);
Because of the exclusion, this is obviously less than ideal, but much better than manually doing the whole thing.
Have your view model map one-to-one with your domain model.
Specify Model as argument for the routeValues as below. This means your view model will be initialized with the values from the domain model. Only the sub set of fields in the form will be overwritten in the resulting personViewData.
Update View:
#model ViewModel.PersonView
#using (Html.BeginForm("Update", "Profile", Model, FormMethod.Post))
{
...Put your sub set of the PersonView fields here
}
ProfileController:
public ActionResult Update(string userName)
{
Person person = _unitOfWork.Person.Get().Where(p => p.UserName == userName).FirstOrDefault();
PersonView personView = new PersonView();
Mapper.Map(person, personView);
return View(personView);
}
[HttpPost]
public ActionResult Update(PersonView personViewData)
{
Person person = _unitOfWork.Person.Get().Where(p => p.UserName == personViewData.UserName).FirstOrDefault();
Mapper.Map(personViewData, person);
_unitOfWork.Person.Update(person);
_unitOfWork.Save();
return Json(new { saved = true, status = "" });
}
Why don't you use TryUpdateModel with the form collection.
If your view is editing a person
public class Person
{
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}
And your view is only editing first name and last name, you can do this:
public ActionResult Action(FormCollection form)
{
Person personToUpdate = Repository.GetPerson(form["ID"]);
TryUpdateModel<Person>(personToUpdate, form);
Repository.Update(personToUpdate)
return View();
}
That will only update Person with the items that a part of the form collection. If you don't want a field updated, don't submit it with the form.
What if you have full model but each page uses and updates only the required part? Then you update the business model using complete view data at the last page.
I use a similar approach to yours (in my case Entity Framework) with Entity -> ViewModel -> View but only on views with "complex" entities that have either 1:M or M:M relationships. In most cases I took the low road and went for Entity->View when I have a simple entity.
My ViewModel is defined as Entity+supporting properties: SelectList or MultiSelectList and either a string or List<string>. I'll also use a ViewModel for instances where I have properties I need for the view but may not necessarily need in the entity (database).
Http Get controller methods are straightforward ActionResults with return View(repository.FetchNewViewModel()) for Create or repository.FetchModelById(id) for Edit. In both instances I'm initializing my entities before passing them to the view.
Create([Bind(Exclude = "Entity.EntityId")] ViewModel model) and Edit(ViewModel model) are the Http Post controller methods of Create and Edit. My Edit view has a hidden input field for EntityId to pass it back and forth.
By the time the Http Post method has the viewmodel, I lose all Entity.Relation and ViewModel.(Multi)SelectList values. I have to rebuild the object if I want my view to display properly:
`
try
{
var tags = model.TagIds; // List<string> or <int> depending on your Id type
if (model.TagsList == null) // It will be
{
model.TagsList = _repository.FetchSelectedTags(tags); // Build a new SelectList
}
if (!ModelState.IsValid)
{
return View(model);
}
_repository.Add(model.Article, tags); // or Save() for Edit
}
catch
{
return View(model); // Generally means something screwed in the repository class
}
return RedirectToAction("Index");
`
There is maybe 30% of my entity base using a ViewModel so I definitely only use it as needed. If you have complex views and model data in most instances you can probably break it down to smaller views.
Right now i´m working on a large project using S#arp Architecture and im also using the approach:
Model -> ViewModel -> Model
I use the ViewModel for the Binding part and Validations, the other approach is to use the Model Directly (with tryUpdateModel / UpdateModel which we used during the prototype develop) but for complex scenarios we end up handling special situation like SelectLists/Checkbox/Projections/HMAC Validations in a little ViewModel anyway and using a lot of Request.Form["key"] =( , the other drawback is handling the errors situations where you want to repopulate the form with the user input, i found it a little more complicated using the Model directly (using a ViewModel we take a lot of advantage of ModelState attempted value, saving us a couple of trips to the DB, anyone who have faced this scenario will know what i mean).
This approach is a bit time consuming, just like you said, you end up matching properties, but in my opinion is the way to go for complex forms.
It worth mentioning that we just use ViewModels for the Create/Edit scenarios, for almost everything else we use directly the model.
I have not use autommapers so far, but definitely i ll give it a try.

Using Include and Exclude in asp.net mvc binding OR creat a new subset object?

Does it make sense create an object that contains only those properties that the user will input on the webpage, use that for binding in the controller, and then map to the full Entity Object? Or should you just use the entity object, and use Include and Exclude to make restrictions on what gets bound on input?
I have come to like the idea of using interfaces to segregate which properties should be included when the object is updated.
For example:
To create and update an person object:
interface ICreatePerson
{
string Name { get; set; }
string Sex { get; set; }
int Age { get; set; }
}
interface IUpdatePerson
{
string Name { get; set; }
}
class Person : ICreatePerson, IUpdatePerson
{
public int Id { get; }
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
}
Then, when binding model, just use the appropriate interface as the type and it will only update the name property.
Here is an example controller method:
public ActionResult Edit(int id, FormCollection collection)
{
// Get orig person from db
var person = this.personService.Get(id);
try
{
// Update person from web form
UpdateModel<IUpdatePerson>(person);
// Save person to db
this.personService.Update(person);
return RedirectToAction("Index");
}
catch
{
ModelState.AddModelErrors((person.GetRuleViolations());
return View(person);
}
}
See this article (and the comments) for a very good discussion of the options.
I recommend using a separate presentation model type in most cases. Aside from the issue of binding (which is important, but there are other ways around this issue), I think that there are other reasons why using presentation model types is a good idea:
Presentation Models allow "view-first" development. Create a view and a presentation model at the same time. Get your user representative to give you feedback on the view. Iterate until you're both happy. Finally, solve the problem of mapping this back to the "real" model.
Presentation Models remove dependencies that the "real" model might have, allowing easier unit testing of controllers.
Presentation Models will have the same "shape" as the view itself. So you don't have to write code in the view to deal with navigating into "detail objects" and the like.
Some models cannot be used in an action result. For example, an object graph which contains cycles cannot be serialized to JSON.

Resources