简体   繁体   中英

N tier architecture c# mvc ViewModels

Assuming i have tables like this:

Users
------
UserId
Email
...

People
------
PersonId
UserId
Name
Country
...

With the corresponding models:

public class User{
   ...
}

public class Person{
   ...
}

I want to have a view in my MVC application showing all people including their email address.

My solution is structured in different projects like:

Comp.App.Services
Comp.App.Dal
Comp.App.Web

Ideally i create a viewmodel in my controller to populate my view later like this:

public class PersonListItemViewModel{
    public string Name { get; set; }
    public string Country { get; set; }
    public string Email { get; set; }
}

Now the problem. I want to get the data with 1 query in my service layer, but the service layer must not know about the viewmodel, and the query should not return any extra field. Only the fields i specified in my viewmodel.

I red many best practices for N-tier applications, but i cant seem to figure out how to implement this without letting my service layer know about view models. Which seems not to be correct. I am always assuming my service layer can only know about domain models, but in real world applications i always walk into the issue that i want to limit my query to for example only 2 fields. This cant be done if my service only talks domain models.

Have i misunderstood something about this approach?

PS. Im using Dapper in my Dal project.

EDIT: Its not a duplicate of Converting DTOs to View Models . Im aware of automapper. Let me ask the question a little different. What should my service return, when i have a method there called: GetPeopleWithEmail() ???

According to what im reading now in the anwsers it should return a DTO, and convert the DTO to a ViewModel in my controller. Correct?

If you are looking to make sure your ViewModels stay in the presentation side and Domain models stay on the Repository side, then I would go with a Logic layer.

  1. Web Layer:

    • View: Takes in a view model
    • Controller: Works with Viewmodel and WebLogic
    • WebLogic: works with Viewmodel, Service , and Domain model
  2. App Layer:

    • Service: works with Domain model and
    • BusinessLogic: works with Repository and Domain model
    • Repository: works with data store

I would, strongly, suggest using some Dependency Injection, such as Unity to help manage the dependencies.

For example, in my above structure, SomeController would take in SomeWebLogic and SomeWebLogic would take in ISomeService. SomeWebLogic would call on ISomeService to convert SomeDomainModel into SomeViewModel which would eventually be used by SomeController.

On the App layer side, SomeService would take in SomeBusinessLogic, which would take in ISomeRepository.

In your example - using my suggested structure:

--Web Layer--

PersonView.cshtml:

@model List<PersonListItemViewModel>;

@foreach(var person in model)
{
   //some html binding
}

PersonController.cs:

public ActionResult PersonView()
{
   var personWebLogic = new PersonWebLogic(); //would suggest DI instead
   var personsModelList = personWebLogic.GetPersons();
   return View(personsModelList );
}

PersonWebLogic.cs

public List<PersonListItemViewModel> GetPersons()
{
   var personService = new PersonService(); //suggest DI here again
   var people = personService.GetPeople(); //returns a list of domain models
   var personsViewModelList = new List<PersonListItemViewModel>();
   foreach(var person in people)
   {
     //use some custom function to convert PersonDomainModel to PersonListItemViewModel
     personalsViewModel.Add(MapPersonDomainToPersonView(person)); 
   }
   return personsViewModelList;
}

--App Layer--

PersonService

public List<Person> GetPeople()
{
   var personLogic = new PersonLogic(); //use DI for this
   return personLogic.GetPeople(); //return type will be dependent on your service architecture
}

PersonLogic

public List<Person> GetPeople()
{
   var personRepostitory = new PersonRepository(); //DI...
   var personDataTable = personRepository.GetAllPeople(); //return type will vary on your repository structure
   //Custom function to map to person list from data table, this could be in repo, all depends on your desired structure
   return MapPersonDataTableToPersonList(personDataTable);
}

PersonRepository

public DataTable GetAllPeople()
{
   var database = GetDatabase();
   var dataTable = ...//call db to get person datatable
   return dataTable;
}

[...] and the query should not return any extra field. Only the fields i specified in my viewmodel.

Actually returning an instance of some model, domain object or whatever in your domain layer which has 10, 20 or 100 properties shouldn't mean that all properties must be set.

In the other hand, the same rule can be applied to data-transfer objects (DTO).

Usually you use JSON serialization format and default ASP.NET Web API JSON serialization works with JSON.NET library, which supports DataContractAttribute and DataMemberAttribute attributes.

At the end of the day, you can send an object over the wire from the API using a DTO class with 20 properties, but only ones with a non-default value will be serialized:

[DataContract]
public class Dto
{
     [DataMember(EmitDefaultValue = false)]
     public string PropertyA { get; set; }
}

If you instantiate Dto and you set no property, the serialization result will be just {} (a blank literal object). Now you can extrapolate this rule to large DTOs and you can be sure that you're not transmitting unwanted properties over the wire. Or in other words, you can use serialization attributes to produce many different DTO from the same class! .

You should care about what properties are being set rather than how many properties exposes some class when working in an n-tier scenario. An use case A may set 3 properties and use case B may set other 2 properties different than use case A. And maybe use case C sets other properties plus ones set by use case A and B.

Sometimes things go harder and you can't use one class to rule them all, and then you implement many data-transfer objects to cover different use cases, where you implement a sub-set of properties required by a concrete client view.

An important point here is ViewModel != DTO . A view model provides behavior and data to views, while a DTO is just an object to transfer a sub-set of data given by some service in order to optimize network performance and usage, and avoid sending unrelated data to other tiers.

In accordance with principles of DDD (domain-driven design) domain entities can be root and child . Root entity is independent and can contains a few of dependent children.

The root entity repository should load entire entity from a database including all necessary children. You can optimize query in the code of the repository.

public class PersonRepository
{
    public Person GetById(int id)
    {
        // reads Person data and related User data
        // builds Person object and returns it
    }
}

Then you can use the root object to build your view/model:

PersonListItemViewModel CreateViewModel(Person person)
{
    return new PersonListItemViewModel
    {
        Name = person.Name,
        Country = person.Country,
        Email = person.User.Email,
    };
}

The service can return all people from a method like All() which returns a IQueryable. Then you can use it to select a custom projection like

db.People.All().Select(p => new PersonListItemViewModel() 
{ 
    Name = p.Name,
    Country = p.Country,
    Email = db.Users.FirstOrDefault(u => u.UserId == p.UserId).Email
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM