asp.net mvc - N tier architecture c# mvc ViewModels
本文关键字:mvc architecture tier ViewModels net asp | 更新日期: 2023-09-27 17:50:01
假设我有这样的表:
Users
------
UserId
Email
...
People
------
PersonId
UserId
Name
Country
...
与对应的模型:
public class User{
...
}
public class Person{
...
}
我想有一个视图在我的MVC应用程序显示所有的人,包括他们的电子邮件地址。
我的解决方案是在不同的项目结构,如:
Comp.App.Services
Comp.App.Dal
Comp.App.Web
理想情况下,我会在我的控制器中创建一个视图模型,以便稍后填充我的视图,如下所示:
public class PersonListItemViewModel{
public string Name { get; set; }
public string Country { get; set; }
public string Email { get; set; }
}
现在问题来了。我想在我的服务层中通过一个查询获得数据,但是服务层必须不知道视图模型,并且查询不应该返回任何额外的字段。只有我在视图模型中指定的字段。
我有很多n层应用程序的最佳实践,但我似乎不知道如何在不让我的服务层知道视图模型的情况下实现它。这似乎是不对的。我总是假设我的服务层只能知道域模型,但在现实世界的应用程序中,我总是遇到这样的问题:我想限制我的查询,例如只有2个字段。如果我的服务只讨论域模型,这是不可能做到的。
我是否误解了这种方法?
p。我在我的Dal项目中使用Dapper。
编辑:它不是将dto转换为视图模型的副本. 我知道自动装置。让我换个角度问这个问题。我的服务应该返回什么,当我有一个方法叫做:GetPeopleWithEmail() ?
根据我现在在答案中阅读的内容,它应该返回一个DTO,并将DTO转换为我的控制器中的ViewModel。正确吗?
如果您希望确保您的ViewModels留在表示端,而域模型留在存储库端,那么我会使用逻辑层。
- Web层:
- View:接受一个视图模型
- 控制器:与Viewmodel和WebLogic一起工作
- WebLogic:与视图模型、服务和域模型一起工作
应用程序层:
- 服务:与域模型和 一起工作
- BusinessLogic:使用Repository和Domain模型
- 存储库:与数据存储 一起工作
我强烈建议使用一些依赖注入,比如Unity来帮助管理依赖。
例如,在上面的结构中,SomeController接收SomeWebLogic, SomeWebLogic接收ISomeService。SomeWebLogic将调用ISomeService将SomeDomainModel转换为SomeViewModel,最终由someecontroller使用。
在App层,someeservice接收SomeBusinessLogic,而SomeBusinessLogic接收isomerrepository。
在你的例子中-使用我建议的结构:
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库,支持DataContractAttribute
和DataMemberAttribute
属性。
在一天结束时,您可以使用具有20个属性的DTO类从API通过网络发送对象,但只有具有非默认值的对象将被序列化:
[DataContract]
public class Dto
{
[DataMember(EmitDefaultValue = false)]
public string PropertyA { get; set; }
}
如果您实例化Dto
并且没有设置属性,那么序列化结果将只是{}
(一个空白文字对象)。现在,您可以将此规则推广到大型dto,并且可以确保不会通过网络传输不需要的属性。或者换句话说,您可以使用序列化属性从同一个类生成许多不同的DTO !。
在n层场景中工作时,您应该关心设置了哪些属性,而不是有多少属性暴露了某个类。用例A可能设置3个属性用例B可能设置另外2个与用例A不同的属性用例C可能设置其他属性加上用例A和B设置的属性
有时事情变得更加困难,你不能用一个类来统治所有的类,然后你实现了许多数据传输对象来覆盖不同的用例,在这些用例中,你实现了具体客户端视图所需的属性子集。
这里重要的一点是ViewModel != DTO
。视图模型为视图提供行为和数据,而DTO只是为了优化网络性能和使用而传输某些服务给出的数据子集的对象,并避免将不相关的数据发送到其他层。
根据DDD(域驱动设计)的原则,域实体可以是根和子。根实体是独立的,可以包含几个依赖的子实体。
根实体存储库应该从数据库加载整个实体,包括所有必要的子实体。您可以在存储库的代码中优化查询。
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
});