繁体   English   中英

N层架构C#MVC ViewModels

[英]N tier architecture c# mvc ViewModels

假设我有这样的表:

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

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

搭配相应型号:

public class User{
   ...
}

public class Person{
   ...
}

我想在我的MVC应用程序中显示所有人员的视图,包括他们的电子邮件地址。

我的解决方案包含在不同的项目中,例如:

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

理想情况下,我在控制器中创建一个viewmodel以便稍后像这样填充视图:

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

现在的问题。 我想在服务层中通过1个查询获取数据,但是服务层必须不了解ViewModel,并且查询不应返回任何额外的字段。 只有我在视图模型中指定的字段。

我为N层应用程序提供了许多最佳实践,但是我似乎无法弄清楚如何在不让我的服务层知道视图模型的情况下实现这一点。 这似乎是不正确的。 我一直以为我的服务层只能知道领域模型,但是在现实世界的应用程序中,我总是遇到我想将查询限制为例如2个字段的问题。 如果我的服务仅使用域模型,则无法完成此操作。

我是否对这种方法有误解?

PS。 我在Dal项目中使用了Dapper。

编辑:它不是将DTO转换为视图模型的副本。 我知道自动映射器。 让我问这个问题有点不同。 当我有一个名为GetPeopleWithEmail()的方法时,我的服务应该返回什么?

根据即时消息在anwsers中读取的内容,它应该返回DTO,并将DTO转换为控制器中的ViewModel。 正确?

如果您希望确保ViewModels停留在表示端而Domain模型却停留在Repository端,那么我将使用Logic层。

  1. 网页层:

    • 视图:引入视图模型
    • 控制器:与Viewmodel和WebLogic一起使用
    • WebLogic:与Viewmodel,Service和Domain模型一起使用
  2. 应用层:

    • 服务:与域模型一起使用
    • BusinessLogic:与存储库和域模型一起使用
    • 存储库:与数据存储一起使用

我强烈建议您使用某种依赖注入,例如Unity来帮助管理依赖。

例如,在我的上述结构中,SomeController将采用SomeWebLogic,而SomeWebLogic将采用ISomeService。 SomeWebLogic将调用ISomeService将SomeDomainModel转换为SomeViewModel,该模型最终将由SomeController使用。

在App层,SomeService将采用SomeBusinessLogic,而ISBusinessRepository将采用。

在您的示例中-使用我建议的结构:

--Web层-

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;
}

-应用层-

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;
}

[...],查询不应返回任何额外的字段。 只有我在视图模型中指定的字段。

实际上返回某个模型,域对象或域层中具有10、20或100个属性的任何对象的实例并不意味着必须设置所有属性。

另一方面,可以将相同的规则应用于数据传输对象(DTO)。

通常,您使用JSON序列化格式和默认的ASP.NET Web API JSON序列化与JSON.NET库一起使用,该库支持DataContractAttributeDataMemberAttribute属性。

在一天结束时,您可以使用具有20个属性的DTO类通过API从电线上发送对象,但是只有具有非默认值的属性才会被序列化:

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

如果实例化Dto且未设置任何属性,则序列化结果将仅为{} (空白文字对象)。 现在,您可以将该规则外推到大型DTO,并且可以确保您不会通过导线传输不需要的属性。 换句话说,您可以使用序列化属性来产生来自同一类的许多不同的DTO!

您应该关心要设置的属性,而不是在n层方案中工作时有多少个属性公开某个类。 用例A可以设置3个属性,用例B可以设置与用例A不同的其他2个属性。用例C可以设置其他属性,以及用例A和B设置的属性。

有时情况变得更加艰难,您不能使用一个类来全部统治它们,然后您实现了许多数据传输对象以涵盖不同的用例,在其中实现了具体客户端视图所需的属性子集。

这里重要的一点是ViewModel != DTO 视图模型视图提供行为和数据,而DTO只是传输某些服务提供的数据子集的对象,以优化网络性能和使用情况,并避免将无关的数据发送到其他层。

根据DDD(域驱动设计)原则,域实体可以是rootchild 根实体是独立的,可以包含一些依赖的子代。

根实体存储库应从数据库加载整个实体,包括所有必需的子代。 您可以在存储库的代码中优化查询。

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

然后,您可以使用根对象来构建您的视图/模型:

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

该服务可以通过All()之类的方法返回所有人,该方法返回一个IQueryable。 然后,您可以使用它来选择自定义投影,例如

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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