I have an ASP.NET MVC 3 application.
I have a Model, ViewModel, View, Controller.
I use Ninject as IoC.
My Controller uses a ViewModel to pass data to the View.
I've started to use Services (concrete and interface types) to take information from the ViewModel and query it against the database to manipulate it.
Can I use the same Service to setup the ViewModel? Or is this going against the grain of the design pattern?
I.e. Can I abstract setting up the ViewModel in the Service layer?
Scenario
The scenario is; my Model has lots of references to other Models, so when I setup the ViewModel in the controller it's to verbose, and I feel the Controller is doing too much. So I want to be able to just do something like:
var vm = _serviceProvider.SetupViewModel(Guid model1Id, Guid model2Id, /*etc..*/)
And the SetupViewModel function in the ServiceProvider would look like this:
public MyModelViewModel SetupViewModel(Guid model1Id, Guid model2Id, /*etc...*/)
{
var vm = new MyModelViewModel();
var model1 = _repository.Model1s.FirstOrDefault(x => x.Id.Equals(model1Id));
var model2 = _repository.Model2s.FirstOrDefault(x => x.Id.Equals(model2Id));
// etc....
vm.Model1 = model1;
vm.Model2 = model2;
return vm;
}
By doing this I could also add some null conditions as well, not worrying about making my Controller really really really big!!
I use 1 ViewModel for the Create/Edit actions. I don't reuse the ViewModel elsewhere.
I would let the service layer return a Domain Model and map it to a ViewModel in the controller.
This way you can use a service method with multiple ViewModels, for a desktop and mobile view for example.
You can let AutoMapper do the hard work for you or do it manually, by creating a constructor in the ViewModel which takes the Domain Model.
The domain model:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
public string Email { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
The ViewModel:
public class CustomerWithOrdersModel
{
public CustomerWithOrdersModel(Customer customer)
{
Id = customer.Id;
FullName = string.Format("{0}, {1}", customer.LastName, customer.FirstName);
Orders = customer.Orders.ToList();
}
public int Id { get; set; }
public string FullName { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
EDIT: AutoMapper example:
The AutoMapper profile containing the mapping from a Customer to a CustomerWithOrdersModel:
public class ViewModelProfile : Profile
{
public override string ProfileName
{
get { return "ViewModel"; }
}
protected override void Configure()
{
CreateMap<Customer, CustomerWithOrdersModel>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Format("{0}, {1}", src.LastName, src.FirstName)))
.ForMember(dest => dest.Orders, opt => opt.MapFrom(src => src.Orders.ToList()));
}
}
Id is mapped by convention.
Extension methods for the ViewModelProfile:
public static class ViewModelProfileExtensions
{
public static CustomerWithOrdersModel ToModel(this Customer customer)
{
return Mapper.Map<CustomerWithOrdersModel>(customer);
}
public static Customer ToEntity(this CustomerWithOrdersModel customerWithOrdersModel)
{
return Mapper.Map<Customer>(customerWithOrdersModel);
}
}
The controller action:
public ActionResult Details(int customerId)
{
Customer customer = _customerService.GetById(customerId);
CustomerWithOrdersModel customerWithOrders = customer.ToModel();
return View(customerWithOrders);
}
If you create a mapping from CustomerWithOrdersModel to Customer, you can use customerWithOrdersModel.ToEntity() to map it back to the Domain Model.
Thats it! You can remove the constructor with the Customer domain model from the ViewModel.
If you have the view models as their own project and handle the mapping and returning of view models in your service layer, I see nothing wrong with that. For separation of concerns you could always have another component that handles the mapping.
Related
I want to use AutoMapper in my .Net 6 APIs to convert the entity model User to DTO model UserDTO.
The User model class is:
public class User : BaseEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public string Avatar { get; set; } = null!;
public string Email { get; set; } = null!;
public ICollection<Book>? FavoriteBooks { get; set; }
}
And the UserDTO is a record as follows:
public record UserDTO(Guid Id, string FullName, string Avatar, string Email);
I have added the required package AutoMapper.Extensions.Microsoft.DependencyInjection v.12.0.0,
and the configuration steps are given below:
1- Create MappingProfile that inherits from the Profile class
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<User, UserDTO>()
.ForMember(
dest => dest.FullName,
opt => opt.MapFrom(src => string.Join(" ", src.FirstName, src.LastName))
);
}
}
2- Register the service in Program.cs file:
builder.Services.AddAutoMapper(typeof(Program));
3- Use the mapper as an injected service inside Service project:
public IEnumerable<UserDTO> GetAllUsers(bool trackChanges)
{
var users = _repository.User.GetAllUsers(trackChanges);
return _mapper.Map<IEnumerable<UserDTO>>(users);
}
When I call the GetAllUsers method in postman, I get the following error:
Error mapping types.
Mapping types:
List -> IEnumerable
After a few days of struggling and searching, I realized that the .ForMember() method breaks the functionality of the profile class. In other words, if I change the UserDTO record:
public record UserDTO(Guid Id, string FirsName, string Avatar, string Email);
the FullName filed changed to FirstName to have compatibility with the User model. Also change the MappingProfile class:
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<User, UserDTO>();
}
}
the GetAllUsers method works as expected. So to conclude, if I add the .ForMember() method to the constructor of the MappingProfile class as in documentation, it breaks the functionality of the CreatMap method.
How should I use the .ForMember() method to map the User model to the corresponding DTO? Is this method obsolete? Is there any replacement for this method?
I found 2 solutions:
Solution 1:
I created a method to get the full name of the user. The method name should be prefixed with get:
The naming convention can cover simpler examples where the source object has a property, method, or method with a “Get” as a prefix with the same name as the property of a destination object.
so I have modified the User model class and added the following method:
public class User : BaseEntity
{
... // User model properties
public string GetFullName() => $"{this.FirstName} {this.LastName}";
}
and removed the .ForMemeber()` method from the profile class:
public MappingProfiles()
{
CreateMap<User, UserDTO>();
}
Solution 2:
It seems that .ForMember() is obsolete, I have found an alternative for that, .ForCtorParam():
public MappingProfiles()
{
CreateMap<User, UserDTO>()
.ForCtorParam(
"FullName",
opt => opt.MapFrom(src => string.Join(" ", src.FirstName, src.LastName))
);
}
In these ways, I have converted my User model class to UserDTO.
I have a project and user Model that inherits the default identity class.
These two share a many to many relationship.
public class Project
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<AppUser> users { get; set; }
}
public class AppUser : IdentityUser
{
public string DisplayName { get; set; }
public ICollection<Project> projects { get; set; }
}
I also have a project controller where I want to display ALL projects that contain the current user. (projects can have multiple users)
I also want to be able to create projects.
[Authorize]
public IActionResult Index(string id)
{
IEnumerable<Project> objProjectList = _unitOfWork.Project.GetAll();
return View(objProjectList);
}
I started off by passing the user id through an anchor tag like so.
<a class="nav-link text-dark"
asp-area="" asp-controller="Project" asp-action="Index"
asp-route-id="#UserManager.GetUserId(User)">Projects</a>
How do I use the id to get the projects that only contain the user corresponding to the id in my project controller?
How can I use the same id to create a project with the user attached on a post route in the same controller?
Should I avoid passing sensitive data like user id through anchor tags and get the user id some other way?
I'd appreciate any input, thanks.
You can try something like this . But is good idea to use ViewModels ti protect your database. Also, all your logic should be in Service classes not in the controllers. The way You pass the Id is totally fine.
public interface IProjectService
{
IEnumerable<Project> GetAllProjectsByUserId(object userId);
}
public class ProjectService : IProjectService
{
public IEnumerable<Project> GetAllProjectsByUserId(string userId)
{
return _unitOfWork.Project.Where(x => x.users.Any(x =>
x.Id = userId)).ToList();
}
}
Give the Service to the dependency container in the StartUp class
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProjectService , ProjectService >();
}
Then you call the Service in the Controller
private readonly IProjectService projectService;
public ControllerName(IProjectService projectService)
{
this.projectService = projectService;
}
[Authorize]
public IActionResult Index(string id)
{
var viewModel = projectService.GetAllProjectsByUserId(id);
return View(objProjectList);
}
Тhere are more things to do, such as repositories, dtos eg, but this will be a good for the beginning
If I have a complex model representing a large amount of data, and I only wish to display a cut-down version of that model (e.g. Name, Description), what is the best approach in MVVM?
Most solutions I can find seem to assume that the data is already present in memory and recommend using a new ViewModel that exposes only the fields required.
However rather than select out all of the data from the database, it would be preferable to select just what is necessary. Do I then create a new model to hold that data? Selecting directly into the ViewModel is possible but feels like the wrong thing to do. Likewise using a new model to represent a different version of the same data also feels off somehow.
What is the accepted method of doing this?
As a simple example (Simple enough class that I wouldn't ordinarily do this):
public class User {
public int UserID {get;set;}
public string FirstName
public string LastName
public int AccessLevelID
public List<Groups> UserGroups
}
but I only really need:
public class PreviewUser {
int UserID
string FirstName
}
You can create another type with is a subset of the business type.
usually this is known as a
DTO - Data transfer Object which encapsulates only what you need. so the database needs to query only the subset of the entity.
public class UserDto
{
public int ID { get;set;}
public string Name{ get;set;}
}
Secondly if you need to add some ui logic to the display it is common to wrap the specific DTO in a more specific UI model.
public class UserUI
{
UserDTO _userDto;
UserUI(UserDTO userDto)
{
_userDto = userDto;
}
public string Name
{
get{return IsAfter_21_hours ? "The user as gone home" : _userDto.Name;}
}
}
the UserViewModel will reference an instance of UserUI.
You can either remove properties you don't need from the model (to slightly improve performance) or you can create a viewmodel that will provide only properties that you want to show.
Here is an example:
public class UserViewModel
{
private readonly User _user;
public UserViewModel(User user)
{
_user = user;
}
public int UserID
{
get { return _user.UserID; }
}
public string FirstName
{
get { return _user.FirstName; }
}
}
...
var viewModels = userRepository.GetUsers().Select(user => new UserViewModel(user));
UPDATED:
If performance is really important for you, you can use inheritance. Base class will be smaller version of the data and derived class will contain complete data. You can use the base class when you need to get only some fields from DB and save bandwidth.
public class BaseUser
{
public int UserID { get; set; }
public string FirstName { get; set; }
}
public class User : BaseUser
{
public string LastName { get; set; }
public int AccessLevelID { get; set; }
public List<Groups> UserGroups { get; set; }
}
There are number of approaches you may use:
use "full version" of source model. Since you're building UI, the user will see only what you want to display;
use view model, and wrap source model into this view model. The implementation is trivial, and amount of data is limited before UI;
use view model, and copy data from source model into this view model. The implementation is more complex (either mapping from existing model, or loading only required data from database), but view model and model are totally decoupled.
Actually, it depends on what is more suitable for you.
Note, that often the difference between "view model" and "model" is blurred. If the model looks like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
...and view model - like this:
public class PersonViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
then throw this view model away. While there's no difference, you don't need to create extra classes.
I think you don't have to create new model class to hold user data for view . Instead create a view model class and map the model properties to VM. See the example below
public class UserViewModel
{
Public UserViewModel(User user)
{
//initialize required viewmodel properties here
}
int UserID {get;set;}
string FirstName{get;set;}
}
I have a class at my Domain layer something as below:
public class Employee : AuditableEntity
{
public string FirstName { get; set; }
public string Surname { get; set; }
public double Salary{ get; set; }
public int PhoneNo { get; set; }
public double Bonus
{
get { return Salary + EmployeeAdditionals.Sum(e => e.Value); }
}
// virtual allow lazy loading
public virtual ReadOnlyCollection<EmployeeAdditional> EmployeeAdditionals { get; private set; }
// Paramterless constructor for EF
public Employee()
{
EmployeeAdditionals = new List<EmployeeAdditional>();
}
public void AddAdditionalInfo(EmployeeAdditional additionalInfo)
{
additionalInfo.Employee = this;
additionalInfo.EmployeeId = Id;
((Collection<EmployeeAdditional>) EmployeeAdditionals).Add(additionalInfo);
}
I then have the following viewModel class in my MVC 5 Application:
public class EmployeeDetailsViewModel : BaseViewModel
{
public EmployeeDetailsViewModel()
{
Employee = new Employee();
}
public Employee Employee { get; set; }
//Other fields
}
public class Employee
{
[DisplayName("Employee First Name")]
public string FirstName { get; set; }
[DisplayName("Employee Surname")]
public string Surname{ get; set; }
public double Salary{ get; set; }
[RegEx for Phone Number and Error Mesage]
public int PhoneNo{ get; set; }
public double Bonus { get; set; }
}
I have mapping then in my controllers and viewModel builder to map my viewmodel to my domain model and domain model back to view model and I am attempting to use AutoMapper for this.
Using manual mapping something like below:
// set domain from viewModel
employee.FirstName= model.Employee.FirstName;
//etc for rest of fields
// set view model from domain
viewModel.Employee.FirstName = employee.FirstName;
//etc for rest of fields
Now in my AutoMapperBootstrapper class in Setup method I have the following:
Mapper.CreateMap<Domain.Models.Employee, Employee>();
Mapper.CreateMap<Employee, Domain.Models.Employee>();
Then I am using this is following in my controller and viewmodel builder:
//set domain from viewModel
Mapper.Map(employee, model.Employee);
// set viewmodel from domain
Mapper.Map(viewModel.Employee, employee);
When I hit a breakpoint on mapping from domain to viewModel I see the model.Employee with the values from screen but when I step over this line the values do not get mapped to domain employee and then the model.Emplyee values are reset to null for the strings, 0.0 for double, etc.
Is there something incorrect with my configuration or use of automapper?
You pass wrong parameter. If you want to use existing instance, make sure first parameter is source and second parameter is destination.
Try to switch the parameter.
//set domain from viewModel (viewModel is the source)
Mapper.Map(model.Employee, employee);
^^^^^ ^^^^^
source destination
// set viewmodel from domain (domain is the source)
Mapper.Map(employee, viewModel.Employee);
^^^^^ ^^^^^
source destination
Shouldn't your map code actually be like this? You have to use instances for Map(), it looks like you are using Types
//set domain from viewModel
Mapper.Map(viewEmployee, new model.Employee());
// set viewmodel from domain
Mapper.Map( employee, new viewmodel.Employee());
This is how I would expect you to use it:
var viewModel = Mapper.Map<ViewModel.Employee>(domainModel); <-- this creates a ViewModel for a Domain Model
and the reverse would be:
var domainModel = Mapper.Map<Domain.Employee>(viewModel);
On client side I display data in a way customized for the view needs.
When the user saves and sends the data to the server I do NOT want to reshape the data on
client side to fit the requirements of my domain.
To reshape my ViewModel to my Model I COULD do this:
var periods = periodListViewModel.CellViewModels
.SelectMany(c => c.Rows)
.Select(
p =>
new Period { LessonNumber = p.LessonNumber });
This code must be put at a place of the correct responsibility.
These are my classes:
public class SavePeriodListViewModel
{
public int SchoolyearId { get; set; }
public IEnumerable<CellViewModel> CellViewModels { get; set; }
}
public class CellViewModel
{
public CellViewModel()
{
Rows = new List<RowViewModel>();
}
public List<RowViewModel> Rows { get; set; }
}
public class RowViewModel
{
public string DayName { get; set; }
public string SchoolclassCode { get; set; }
public string Content { get; set; }
public int LessonNumber { get; set; }
}
public class LessonplannerResponse
{
public int SchoolyearId { get; set; }
public IEnumerable<Period> Periods { get; set; }
}
All data I need for my model Period is inside the RowViewModel.
I need to reshape my SavePeriodListViewModel to the LessonplannerResponse
Of course AutMapper can handle the RowViewModel to Period mapping but my problem is not in that area.
AutoMapper is mapping not reshaping collections.
Where should I put the linq reshaping code? Or more general asked where should I put such code in asp.net mvc ?
client side?
controller action?
service is no option as it should not know the viewmodel.
It would appear that the mapping needs to happen inside the controller. I would expect that the Controller Action is defined something like:
public ActionResult SomeAction(SavePeriodListViewModel model)
{
// transform from model to LessonplannerResponse
// validate model
// do something with the model and return a view
}
From the look that we have into your processes, it doesn't appear to be the responsibility of a service to do the transformation, nor does it appear to be the responsibility of the client side -- especially since you already ruled out the client side in your question.
Here is example of how i do it to ViewModel you can also implement it visa verse.
Mapper.CreateMap<Faq, FaqViewModel>()
.ForMember(x => x.Answer, opt => opt.MapFrom(src => src.Answer))
.ForMember(x => x.Question, opt => opt.MapFrom(src => src.Question));
var sourceOfModels = _repository.GetFaqListByAppId(id);
var resultOfViewModels = Mapper.Map<List<Faq>, List<FaqViewModel>>(list.ToList());
This is works perfect for me.