Currently I use private static methods in my controller file to map domain model to view model and vice-versa. Like below:
public ActionResult Details(int personID)
{
Person personDM = service.Get(personID);
PersonViewModel personVM = MapDmToVm(personDM);
return View(personVM);
}
private static PersonViewModel MapDmToVm(Person dm)
{
PersonViewModel vm;
// Map to VM
return vm;
}
Is there any other standard way to do this?
I prefer to put the mapping logic inside the view model (dto) class, because we want to keep the domain model as clean as possible and also the domain model might change overtime.
public class Person
{
public string Name {get; set;}
}
public class PersonViewModel
{
public string Text {get; set;}
public static implicit operator PersonViewModel(Person dm)
{
var vm = new PersonViewModel { Text = dm.Name };
return vm;
}
public static implicit operator Person(PersonViewModel vm)
{
var dm = new Person { Name = vm.Text };
return dm;
}
}
and use it in the controller without explicit casting.
Person dm = service.Get(id);
PersonViewModel vm = dm;
Since the mapping is not always trivial, I think that it might be better to separate it into a different class other than the viewmodel.
That way each class has its own single responsibility. You might want to add an extension method to your domain model, something like:
public static MyViewModel ToViewModel(this MyDomainModel model)
{
// mapping code goes here
}
You also might consider using automapper and call its Map method from your controller.
Related
Something strange is happening in my umbraco project where I have a repository set up like so;
public class HireItemsRepo:BaseGenericRepository<YouHireItContext,HireItem>
{
public List<HireItemViewModel> PopulateHireItemViewModel(RenderModel model)
{ List<HireItemViewModel> HireItems = new List<HireItemViewModel>();
foreach (var Hireitem in base.GetAll())
{
HireItems.Add(
new HireItemViewModel(model.Content)
{
Title = Hireitem.Title,
Price = Hireitem.Price
}
);
}
return HireItems;
}
}
which I'm using in my controller like this
public class HiresController : RenderMvcController
{
// GET: Hire
public override ActionResult Index(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return View("Hires",VM.ToList());
}
}
And using that model in the view like this;
#model List<You_Hire_It.Models.HireItemViewModel>
/*HTML starts here*/
It's strange because if I try to use that model as a List, Umbraco will blow up with the following error;
Cannot bind source type System.Collections.Generic.List`1[[You_Hire_It.Models.HireItemViewModel, You_Hire_It, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to model type Umbraco.Web.Models.RenderModel.
However, if I refactor all the code to use the model on it's own as if I only have one set of values to use, it has no problem with it!
Could anybody point me in the right direction with this please?
Many thanks in advance!
You can inherit from RenderModel as DZL suggests. However, I generally prefer to use route hijacking which would enable me to keep my models simple.
Instead of the Index method in your RenderMvcController, you can create a method with the same name as your view. I note your view is called Hires. So change your controller code to this:
public class HiresController : RenderMvcController
{
// GET: Hire
public ActionResult Hires(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return CurrentTemplate(VM)
}
}
You now need to have your view inherit from UmbracoViewPage. So at the top of your view replace the #model line with the following:
#inherits UmbracoViewPage<List<HireItemViewModel>>
Your model in the view is now of type List<HireItemViewModel> which I think is what you want.
So to iterate the items you would use:
#foreach(var item in Model){
{
// etc
}
Additionally, as this view now inherits from UmbracoViewPage, you have access to the UmbracoContext - just use #Umbraco
For example:
#Umbraco.TypedContentAtRoot().Where(x=>x.DocumentTypeAlias == "HomePage")
or
#Umbraco.AssignedContentItem etc
That is because the model you return from the action need to be of type RenderModel or inherit from it and in your case you are returning a List.
So your model should look something like this:
public class ViewModel : RenderModel
{
public ViewModel(IPublishedContent content) : base(content) { }
public List<HireItem> HireItems { get; set; }
}
public override ActionResult Index(RenderModel model)
{
var vm = new ViewModel(model);
vm.HireItems = new HireItemsRepo().GetHireItems();
return View("Hires", vm);
}
I have a controller that uses two classes. One is called IndexModel and the other IndexViewModel.
I pass the IndexViewModel into the IndexModel constructor.
[HttpGet]
public ActionResult Index()
{
var model = new IndexModel(new IndexViewModel());
var vm = model.GetViewModel();
return View("Index", vm);
}
Here is the view model class. Notice that the setter is private.
public class IndexViewModel
{
public IList<SelectListItem> SelectListItems { get; private set; }
public IndexViewModel()
{
this.SelectListItems = new List<SelectListItem>();
}
}
Here is the Model. When GetViewModel is called the SelectListItems list is populated and the view model returned.
public class IndexModel
{
private IndexViewModel vm;
public IndexModel(IndexViewModel IndexViewModel)
{
this.vm = IndexViewModel;
}
public IndexViewModel GetViewModel()
{
this.FillSelectListItems();
return vm;
}
private void FillSelectListItems()
{
// this data is pulled in from another class that queries a database...
var data = ...
foreach (var itm in data)
{
vm.SelectListItems.Add(new SelectListItem()
{
Value = itm.Id.ToString(),
Text = itm.Text,
});
}
}
}
I would appreciate any comments on how this is currently structured, but my main questions are:
Is it bad practice to write a bunch of methods, like FillSelectListItems(), that alter collection data and don't return a result?
Should I make the setter public so I can just return a list from my method and set the view model property that way?
What do you have to gain by making it private? A headache... make it public :)
There aren't any problems using view models in other view models... Imagine having a blog post... BlogPostViewModel... you would expect it to also have comments right? BlogPostViewModel > CommentViewModel
public class BlogPostViewModel
{
public string BlogPostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<CommentViewModel> Comments { get; set; }
}
Now when you render that, on your PostsController, at Posts/{id}, the view Posts/Index.cshtml would be rendered, and your comments can be rendered inside a partial view...
// Take this as pseudo code as there's some syntatic errors (can't be asked to open VS)
#for(int i = ... i < Model.Comments.Length... i++){
this.Html.Partial("_CommentsPartial.cshtml, Model.Comments[i])
}
On another note, if you wanted, you could pass your Model to the view as a JSON object as well without with this neat little hack... In your controller action...
this.ViewBag.Json = JsonConvert.SerializeObject<TViewModel>(viewModel);
And in your view just pick it backup...
<script> var json = #this.ViewBag.Json </script>
Hopefully this has provided some insight with regards to the purpose these View Models serve...
I have a class of properties which are set from a service which I need available on every view of my MVC application.
Therefore I've created a "Base View Model" which my view models will inherit from.
public class BaseModel
{
public BaseModel()
{
foo = "foo value";
bar = "bar value";
}
public string foo { get; set; }
public string bar { get; set; }
}
public class HomeIndexViewModel : BaseModel
{
}
I have then created a "Base Controller" which all my controllers will inherit from:
public class BaseController : Controller
{
public BaseController()
{
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
HomeIndexViewModel model = new HomeIndexViewModel();
return View(model);
}
}
This is working as expected and I can call #Model.foo in my view and get foo value.
However I don't believe I should be initialising the values of BaseModel in it's constructor as this isn't using Dependency Injection and will become difficult to unit test.
How can I move the setting of the values foo and bar into the BaseController?
Of course I could set the values in the HomeController, but I would rather abstract this from the controller as the logic will always be the same and would bloat all my controllers.
I think the problem is that you are creating the instance of your models inside of the action, so the base controller has no reference to the object to set the properties.
Personally I would probably opt for some 'factory-type' function in the base controller that is responsible for creating the models as you need them.
Something like this for example:
public class BaseController : Controller
{
public T CreateBaseModel<T>() where T : BaseModel, new()
{
return new T
{
foo = "foo value",
bar = "bar value"
};
}
}
Then when you create your models in the actions you can do them like this:
HomeIndexViewModel model = CreateBaseModel<HomeIndexViewModel>();
If for some reason you need to pass parameters to your model constructor then you can have an overload like this:
public T CreateBaseModel<T>(params object[] args) where T : BaseModel
{
T model = (T)Activator.CreateInstance(typeof(T), args);
model.foo = "foo";
return model;
}
HomeIndexViewModel model = CreateBaseModel<HomeIndexViewModel>(param1, param2, etc);
Alternative
The main benefit of the above method is that you can access the foo and bar properties within the action code. However, if you don't care about this and only need the values to be accessible from within the View page, then you can override the OnActionExecuted method and apply the values in there. The benefit of this approach is that you don't need to change the way your models are created in the actions...
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
BaseModel model = filterContext.Controller.ViewData.Model as BaseModel;
if (model != null)
{
model.foo = "foo value";
model.bar = "bar value";
}
base.OnActionExecuted(filterContext);
}
Having the null check in there means it will only try to apply the values for models that inherit from BaseModel, which means you can still use other models without worry.
With this approach, your action code goes back to how it was originally:
HomeIndexViewModel model = new HomeIndexViewModel();
return View(model);
I'm trying to work out the best architecture for handling model type hierarchies within an MVC application.
Given the following hypothetical model -
public abstract class Person
{
public string Name { get; set; }
}
public class Teacher : Person
{
public string Department { get; set; }
}
public class Student : Person
{
public int Year { get; set; }
}
I could just create a controller for each type. Person would have just the index and detail action with the views making use of display templates, Teacher and Student would have just the Create/Edit actions. That would work but seems wasteful and wouldn't really scale because a new controller and views would be needed if another type was added to the hierarchy.
Is there a way to make a more generic Create/Edit action within the Person controller? I have searched for the answer for a while but can't seem to find exactly what I am looking for so any help or pointers would be appreciated :)
Sure, but it takes a little leg work.
First, in each of your edit/create views, you need to emit the type of model you are editing.
Second, you need add a new modelbinder for the person class. Here is a sample of why I would do for that:
public class PersonModelBinder :DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
PersonType personType = GetValue<PersonType>(bindingContext, "PersonType");
Type model = Person.SelectFor(personType);
Person instance = (Person)base.CreateModel(controllerContext, bindingContext, model);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, model);
return instance;
}
private T GetValue<T>(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult =bindingContext.ValueProvider.GetValue(key);
bindingContext.ModelState.SetModelValue(key, valueResult);
return (T)valueResult.ConvertTo(typeof(T));
}
}
Register it in your app start:
ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
The PersonType is what I tend to use in each model and is an enum that says what each type is, I emit that in a HiddenFor so that it comes back in with the post data.
The SelectFor is a method that returns a type for the specified enum
public static Type SelectFor(PersonType type)
{
switch (type)
{
case PersonType.Student:
return typeof(Student);
case PersonType.Teacher:
return typeof(Teacher);
default:
throw new Exception();
}
}
You can now do something like this in your controller
public ActionResult Save(Person model)
{
// you have a teacher or student in here, save approriately
}
Ef is able to deal with this quite effectively with TPT style inheritance
Just to complete the example:
public enum PersonType
{
Teacher,
Student
}
public class Person
{
public PersonType PersonType {get;set;}
}
public class Teacher : Person
{
public Teacher()
{
PersonType = PersonType.Teacher;
}
}
I have the following model:
public class Person
{
public string LastName{get;set;}
public City City {get;set;}
}
public class City
{
public string Name {get;set;}
}
I have two Views:
One for display all Persons with LastName and the Name of the city in
a DataGrid(AllPersonsViewModel)
One for adding a new Person (PersonViewModel)
My AllPersonsViewModel:
public class AllPersonViewModel : ViewModel
{
public ObservableCollection<PersonViewModel> PersonViewModels {get;set;}
}
I started with the following PersonViewModel:
public class PersonViewModel : ViewModel
{
private Person _person;
public string Name
{
get { return _person.Name;}
set { _person.Name = value; RaisePropertyChange("Name");}
}
public string CityName
{
get { return _person.City.Name;}
}
}
Then I added the properties for adding a new Person. In the View I need a Textbox for the PersonName and a Combobox for selection of a City:
public class PersonViewModel : ViewModel
{
private Person _person;
public string Name
{
get { return _person.Name;}
set { _person.Name = value; RaisePropertyChange("Name");}
}
public string CityName
{
get { return _person.City.Name;}
}
public City SelectedCity
{
get { return _person.City;}
set { _person.City = value; RaisePropertyChange("SelectedCity");}
}
public ObservableCollection<City> Cities {get;set;}
}
Is this the right approach? It seems a little bit redundant to me. In the Grid of AllPersonsView I could also bind directly to the "SelectedCity.Name" instead of the extra property CityName. The grid is also readonly.
you have multiple problems;
1 - you do not need to declare an observable collection of viewmodels in AllPersonViewModel. Just declare an ObservableCollection of Person.
2 - do not add the CityName property; not needed as you have stated.
3- do not add the Name property. Bind the textbox to Name property of the Person.
Your question really boils down "is it OK to expose my model directly to the view?" Some purist will say no while other will say that having a view model that wraps a model without adding any new functionality is redundant.
In my opinion it depends on the task at hand but "skiping" a view model may come back and bite you later when you need to add additional state that doesn't belong in the model. If in doubt use a view model but for instance when exposing simple model objects in a list you often don't need the extra layer the view model provides.
In your case you have opted for the "purist" solution and because your model object doesn't support INotifyPropertyChanged you can't get rid of the view model if a model property is changed by multiple sources. But instead of providing a CityName property you could bind to SelectedCity.Name. WPF supports property navigation in data binding expressions.
For more insight into this topic you can google mvvm expose model.