I have an application with a Company model. The Company model has a navigation property to an Address model (one-to-one relationship):
Company.cs
public class Company
{
public int CompanyID { get; set; }
public string Name { get; set; }
// Snip...
public virtual Address Address { get; set; }
}
I've created a view model to handle the edit, detail, and create actions:
CompanyViewModel.cs
public class CompanyViewModel
{
public int CompanyID { get; set; }
[Required]
[StringLength(75, ErrorMessage = "Company Name cannot exceed 75 characters")]
public string Name { get; set; }
// Snip...
public Address Address { get; set; }
}
I'm using AutoMapper in my controller to map back and forth between the model and view model, and everything is working properly. However, I now want to use validation on the address object - I do not want a company to be created without an address being present.
My first thought was the simple route - I tried putting a '[Required]' annotation on the Address property. This didn't do anything.
I then thought it would be better to do away with the Address property and abstract that data in the view model, so I added properties to the view model for all the properties in my Address class:
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
// etc....
This seemed like good practice, but now my AutoMapper can't map these properties to the Company class' Address object, so I had to manually map in the controller:
public ActionResult Details(int id = 0)
{
// Snip code retrieving company from DB
CompanyViewModel viewModel = new CompanyViewModel();
viewModel.Name = company.Name;
viewModel.Address1 = company.Address.Address1;
// Snip...
return View(viewModel);
}
This leads to a lot of extra code in my controller instead of a nice one-line AutoMapper statement...so what's the right way to deal with this (validation of nested models in a view model)?
Is it good practice to expose the Address property directly in the view model, or better to abstract it out with separate properties like I have done?
Can AutoMapper work in a situation where source and destination are not exact matches?
if you want automapper to be able to map your properties from model to your viewmodel without specifying the mappings explicitly, you've got to use the "flattenting convention" : means that you must concatenate the navigation property's name with its property names.
So your ViewModel should contain
public int CompanyID { get; set; }
[Required]
[StringLength(75, ErrorMessage = "Company Name cannot exceed 75 characters")]
public string Name { get; set; }
// Snip...
//Address is the navigation property in Company, Address1 is the desired property from Address
public string AddressAddress1 { get; set; }
public string AddressAddress2 { get; set; }
public string AddressCity { get; set; }
public string AddressPostalCode { get; set; }
}
by the way, you can also tell AutoMapper to map properties which don't respect the naming convention explicitly :
Mapper.CreateMap<Company, CompanyViewModel>()
.ForMember(dest => dest.Address1, opt => opt.MapFrom(src => src.Address.Address1));
Related
I have this simple data model:
// Model
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
.... Another values here ....
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
.... Another values here ....
}
// ViewModel
public class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
I want to map (using AutoMapper) the values of PersonViewModel to the corresponding properties (where AutoMapper discovers if the property should be in the root object or inside the sub-object). Keeping in mind, AutoMapper should not create neither Person object nor Address (the objects must be created manually to fill another properties before auto mapping), and AutoMapper uses the already existed objects. For example:
var addressObj = new Address
{
... Filling some values...
};
var personObj = new Person
{
Address = addressObj;
... Filling some values...
};
mapper.Map(personViewModelObj, personObj); // How to make this work for both Person and Address properties?
How can I get that auto mapping to work for both person properties and address properties?
Should I add two mapping rules (for address and for person), and execute mapper.Map() twice?
Using #Jasen comments I got it working. The main problem was that I am mapping in a reversed direction. This sentence in official documentation solves the problem:
Unflattening is only configured for ReverseMap. If you want unflattening, you must configure Entity -> Dto then call ReverseMap to create an unflattening type map configuration from the Dto -> Entity.
Here is the link:
https://github.com/AutoMapper/AutoMapper/blob/master/docs/Reverse-Mapping-and-Unflattening.md
In other words, to get unflattering to work, I have (or must) to map in this direction:
CreateMap<HierarchicalObject, FlattenedObject>()
.ReverseMap();
I have an entity:
public class Organization
{
public int OrganizationId { get; set; }
[Required]
[StringLength(160)]
public string Name { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Team> Teams { get; set; }
public virtual ICollection<ApplicationUser> Persons { get; set; }
}
I have created a OrganizationViewModel for representing data to view as well as creating new entities. I later map it using Automapper before saving changes to database using Entity Framework...
public class OrganizationViewModel
{
public int OrganizationId { get; set; }
[Required]
[StringLength(160)]
public string Name { get; set; }
public List<ApplicationUser> Persons { get; set; }
}
I have created an action method Edit which should only allow user to edit property Name (for this example only, but in reality, it does update many other properties which I have slimmed down for this question)..
The Edit view only has two properties #Html.HiddenFor(model => model.OrganizationId) and #Html.EditorFor(model => model.Name)
Now in [HttpPost]
I got OrganizationViewModel with updated Name property but property Persons remains null, if there are users already present in an organization EF throws an error as it is not expected by the context.
I could have updated the properties I want to update EXPLICITLY in Post method but I don't want to do everything manually. Is there any other way where I can say that just update the entities I want. I tried using Bind's Include and Exclude but it has null for Persons as well.
Help me :)
If you create a view model that contains only the OrganizationId and Name properties that are edited by this view, you can achieve it by executing
db.Entry(db.Organizations
.First(x => OrganizationId == viewModel.OrganizationId))
.CurrentValues.SetValues(yourViewModel);
yourViewModel - can be any object with set of properties that you want to update, so you don't need to repeat your code but you have to have a specific view model for each operation. You may have it even locally for the update method and automap to it but it must have only properties you want to update.
As my domain classes I have Person and FavoritePerson classes as follows.
public class CompanyPerson : ICompanyPerson
{
[Key]
public Guid PersonId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
public class CompanyFavoritePerson : IFavoritePerson
{
[Key]
public Guid FavoritePersonId { get; set; }
[Column(TypeName = "datetime2")]
public DateTime CreateDate { get; set; }
public Guid? CompanyPerson_PersonId { get; set; }
[StringLength(128)]
public string CompanyUser_UserId { get; set; }
public virtual CompanyPerson CompanyPerson { get; set; }
public virtual CompanyUser CompanyUser { get; set; }
}
In my web application I will need to show List of Favorite Person. So my view model is like this;
public class FavoritePersonViewModel
{
public Guid FavoritePersonId { get; set; }
public DateTime CreateDate { get; set; }
public Guid? CompanyPerson_PersonId { get; set; }
public string CompanyUser_UserId { get; set; }
//Option1: PersonViewModel PersonViewModel {get; set; }
//Option2: public string Title {get;set;}
}
Since I need to show Title of the favorite user in the list (where title belongs to Person class) which way will match with best practices?
Referencing a viewModel from another viewModel or extend viewModel with required extra attributes and fill them in business layer?
After some more research on this topic; I found out at this question
What is ViewModel in MVC?
it is clearly stated that:
View models can combine values from different database entities.
As like below;
So now you have data from the Employees and Departments tables in one
view model. You will just then need to add the following two
properties to your view model and populate it with data:
public int DepartmentId { get; set; }
public IEnumerable<Department> Departments { get; set; }
So I am going with Option 2.
The ViewModel pattern is just one of many patterns that fall into the 'Separated Presentation Pattern' bucket.
It's very important that you think about the requirements of your view before designing the ViewModel. For instance, if you have two widgets in your view and every widget has its own ViewModel, composite ViewModel is suitable in the situation, but if the view is just one that uses multiple domain classes, whether you have View model for each one, composite ViewModel is not suitable because it increases the complexity and every change in one ViewModel can break your code.
Thus, based upon your question
As my domain classes I have Person and FavoritePerson classes.
Since I need to show Title of the favorite user in the list (where title belongs to Person class).
It seems to me that composite ViewModel is not a good choice and you should design a new ViewModel.
It is also worth to read the ViewModel Best Practices
I'm following along the ASP.NET MVC 5 book, but I've ran into an itch that the book doesn't seem to scratch. I have an Album model as so:
namespace MvcMusicStore.Models
{
public class Album
{
public virtual int AlbumId { get; set; }
public virtual int GenreId { get; set; }
public virtual int ArtistId { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
public virtual string AlbumArtUrl { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
}
}
To make a long story short, the Genre and Artist models both have a field called Name. When I list these using the StoreManagerController, it displays simply as "Name" in each of the headers. I can add a DataAnnotation to Genre and Artist suchs as [Display(Name="Artist Name")], but I only want it to display as "Artist Name" in this particular instance. I don't want it to be so specific when I am on the "Edit Artist Page."
I understand that I should go about doing this by using a ViewModel, but I am still confused as the view model would still just be pulling in the object, and that object's Display annotations are set in the model itself.
Or better yet, is this something that's best left to the markup?
Not if you use view models properly. Many people end up creating view models like:
public class FooViewModel
{
public Foo MyFoo { get; set; }
}
That's just a waste of time. Instead, you view models should completely stand in for whatever entity your editing, which means, instead of just referencing the entity, you create properties in your view model for all the properties in your entity that you want to view/edit. Then, in your controller actions, you "map" to and from your entity and view model, which is to say, you just set the properties on one with the values of the appropriate properties on the other.
In your situation then, you would need something like:
public class AlbumViewModel
{
public string Title { get; set; }
public decimal Price { get; set; }
public string AlbumArtUrl { get; set; }
public GenreViewModel Genre { get; set; }
public ArtistViewModel Artist { get; set; }
}
public class ArtistViewModel
{
public string Name { get; set; }
...
}
public class GenreViewModel
{
...
}
Then, you can set the display name to be whatever you want on this view model. If you need a different display name in another context, create a separate view model for that.
Also, what's up with all the virtuals? The virtual keyword merely means that the property/method can be overridden by a subclass. While it technically doesn't hurt anything to just make everything virtual, it's code smell unless you truly intend something to be overridden, or even subclassed in the first place. Traditionally, on entities, the only thing you'll ever add virtual to is navigation properties, as this allows Entity Framework to apply its lazy loading logic to your entity. (It literally creates subclasses of your entities dynamically, called "proxies", that add the lazy loading logic to the navigation properties' getter.) If you don't have a navigation property or even if you just don't want lazy loading enabled for that navigation property, then you shouldn't use virtual, unless you really mean to.
I would probably do something like this.
public class AlbumViewModel
{
public int AlbumId { get; set; }
public AlbumGenre Genre { get; set; }
public AlbumArtist Artist { get; set; }
}
[MetadataType(typeof(AlbumArtistMetadata))]
public class AlbumArtist : Artist {
private class AlbumArtistMetadata {
[Display(Name="Artist Name")]
public string Name { get; set; }
}
}
[MetadataType(typeof(AlbumGenreMetadata))]
public class AlbumGenre : Genre
{
private class AlbumGenreMetadata
{
[Display(Name = "Genre Name")]
public string Name { get; set; }
}
}
Though I'm not sure I'd inherit from the entities, but instead create models based on the entities.
one for customer, and one for address.
Required Functionality When a customer registers, they enter their personal details such as name, tel as well as their address details in the same view.
Current Functionality
At present, EF scaffolding provides a dropdown list of addresses to choose from.
Current Code
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
[Required]
public string Surname { get; set; }
[Required]
...
Customer Fields
...
public int AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
[Key]
public int AddressId { get; set; }
...
Address Fields
...
// Navigation Properties
public virtual List<Customer> Customer { get; set; }
}
When seeding the database, I can do so as follows:
new List<Customer>
{
new Customer
{
** Customer Fields ** ,
Address = new Address { ** Address Fields ** }
}
}.ForEach(c => context.Customers.Add(c));
base.Seed(context);
My thoughts
My initial thoughts are that I should create a 3rd Data model called CustomerWithAddress which is essentially a composite of customer and address models. This would allow me to scaffold a strongly typed view.
Alternatively, is it possible for a controller to pass 2 models to 1 view?
I don't know if this is the best way of tackling this problem, or in fact if it is possible. Any thoughts?
If your customer model has an address property, then you will be able to access it from your view. e.g.
#Html.DisplayTextFor(x => model.Address.Addressline1)
Your viewmodel Idea is a good one, but it's not necessary for this particular case.
EDIT: As my friend below pointed out, you may need to manually load the Address property if you are employing lazy loading.