今天谈谈《构建应用层服务》。
理论学习:
应用服务提供了一些门面样式方法来分离表现层和领域层。这样做的目的也是为了解耦,以后表现层就不用直接和业务逻辑层(核心层)打交道了,而是通过应用服务层(相当于媒介)来处理。应用服务层不仅定义了很多服务方法供表现层直接调用,而且还提供了一些Dtos(Data Transfer Object)。
说到Dto,好处确实挺多的。
第一,可以将它理解为一个简化的实体,更方便。比如,我们有一个User表,里面有Id,Name,Password,IsDeleted,CreationTime等等。那么我们简化的UserDto对象就只含有Name和IsDeleted两个字段就够用了,因为表现层就只用到了这两个字段。
第二,更安全,性能更好。如果不用Dto而用实体类的话,最后生成的查询就会将实体的所有字段都会查询出来。这样一来就暴露了一些重要的数据给一些我们不想这些数据被看到的人。
第三,应用扩展性更好,耦合度降低。表现层是通过一个Dto对象作为参数来调用应用服务方法的,然后使用领域对象(实体类对象)执行特定的业务逻辑并返回一个Dto对象给表现层,所以,表现层完全独立于领域层。在一个理想的应用中,表现层和领域层不会直接打交道。
第四,序列化问题。当返回一个数据对象到表现层时,很可能会在某个地方序列化。比如,在一个返回JSON的MVC方法中,你的对象可能会被序列化成Json发送到客户端。如果返回一个实体的话会有问题的。比如,这个User实体有一个Role的应用,如果要序列化User的话也会序列化Role。甚至Role可能有List<Permission>,Permission又有一个PermissionGroup的引用等等…你敢想象序列化之后的对象吗?亦可以轻易地一下子序列化整个数据库。所以,在这种情况下返回一个安全序列化的、特别设计的DTOs是一个好的做法。
先来定义Dtos,看下面代码:
namespace Noah.ChargeStation.Application.CitiesApp.Dto{ public class CityInput : IInputDto { public string Name { get; set; } public string Code { get; set; } public string ProvinceCode { get; set; } } public class GetCityInput : IInputDto { public string Name { get; set; } public string ProvinceCode { get; set; } } public class CreateCityInput : IInputDto, IShouldNormalize { [Required] public string Name { get; set; } [Required] public string Code { get; set; } [Required] public string ProvinceCode { get; set; } public DateTime UpdatedTime { get; set; } public string UpdatedBy { get; set; } public void Normalize() { if (UpdatedTime==null) { UpdatedTime=DateTime.Now; } } }}
这个是输入方向的Dto,实现了IInputDto接口,这样的话ABP可以自动帮助我们进行数据校验。当然,我们也可以添加数据注解进行校验。校验之后,还可以实现IShouldNormalize接口来设置缺省值。
namespace Noah.ChargeStation.Application.CitiesApp.Dto{ public class CityOutput:IOutputDto { public string Code { get; set; } public string Name { get; set; } public string ProvinceCode { get; set; } } public class GetCitiesOutput:IOutputDto { public ListCities { get; set; } }}
以上是输出方向的Dto。
接下来我定义一个城市表的服务接口ICityAppService,我的命名规范是”I+实体类单数+AppService”。
namespace Noah.ChargeStation.Application.CitiesApp{ public interface ICityAppService:IApplicationService { GetCitiesOutput GetCities(GetCityInput input); TaskGetCitiesAsync(GetCityInput input); void UpdateCity(CityInput input); Task UpdateCityAsync(CityInput input); void CreateCity(CityInput input); Task CreateCityAsync(CityInput input); }}
以上定义的方法有同步和异步两个版本。
接下来实现应用服务接口,这里只实现一个方法GetCities(…),望读者举一反三。
public class CityAppService : ChargeStationAppServiceBase, ICityAppService { private readonly IRepository_cityRepository; public CityAppService(IRepository cityRepository) { _cityRepository = cityRepository; } public GetCitiesOutput GetCities(GetCityInput input) { //根据不同条件进行查询不同的结果 //Mapper.CreateMap (); //根据城市名称查询城市数据 if (!string.IsNullOrEmpty(input.Name)) { var cityEntity = _cityRepository.GetAllList(c => c.Name == input.Name).FirstOrDefault(); return new GetCitiesOutput() { CityDto = Mapper.Map (cityEntity) }; } //根据省份编码查询城市数据 if (!string.IsNullOrEmpty(input.ProvinceCode)) { var cityEntityList = _cityRepository.GetAllList(c => c.ProvinceCode == input.ProvinceCode); return new GetCitiesOutput() { CityDtoList = Mapper.Map
>(cityEntityList) }; } return null; } public void UpdateCity(CityInput input) { Logger.Info("Updating a City for input: " + input); } public void CreateCity(CityInput input) { //var city = _cityRepository.FirstOrDefault(c => c.Name == input.Name); //if (city != null) //{ // throw new UserFriendlyException("该城市数据已经存在!"); //} //city = new City() { Code = input.Code, Name = input.Name, ProvinceCode = input.ProvinceCode }; //_cityRepository.Insert(city); } public Task GetCitiesAsync(GetCityInput input) { throw new System.NotImplementedException(); } public Task UpdateCityAsync(CityInput input) { throw new System.NotImplementedException(); } public Task CreateCityAsync(CityInput input) { throw new System.NotImplementedException(); } }
这里又出现了个新东西AutoMapper,关于这个的使用,我这几天会专门开一个关于AutoMapper的专题,敬请期待。代码没有难度,也就不多做解释了。今天就到这里吧。下次再讲。