您好,欢迎访问一九零五行业门户网

ABP入门系列(5)——创建应用服务

一、解释下应用服务层
应用服务用于将领域(业务)逻辑暴露给展现层。展现层通过传入dto(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务逻辑并且将dto返回给展现层。因此,展现层和领域层将被完全隔离开来。
以下几点,在创建应用服务时需要注意:
在abp中,一个应用服务需要实现iapplicationservice接口,最好的实践是针对每个应用服务都创建相应继承自iapplicationservice的接口。(通过继承该接口,abp会自动帮助依赖注入)
abp为iapplicationservice提供了默认的实现applicationservice,该基类提供了方便的日志记录和本地化功能。实现应用服务的时候继承自applicationservice并实现定义的接口即可。
abp中,一个应用服务方法默认是一个工作单元(unit of work)。abp针对uow模式自动进行数据库的连接及事务管理,且会自动保存数据修改。
二、定义itaskappservice接口
1, 先来看看定义的接口
public interface itaskappservice : iapplicationservice { gettasksoutput gettasks(gettasksinput input); void updatetask(updatetaskinput input); int createtask(createtaskinput input); task<taskdto> gettaskbyidasync(int taskid); taskdto gettaskbyid(int taskid); void deletetask(int taskid); ilist<taskdto> getalltasks(); }
观察方法的参数及返回值,大家可能会发现并未直接使用task实体对象。这是为什么呢?因为展现层与应用服务层是通过data transfer object(dto)进行数据传输。
2, 为什么需要通过dto进行数据传输?
总结来说,使用dto进行数据传输具有以下好处。
数据隐藏
序列化和延迟加载问题
abp对dto提供了约定类以支持验证
参数或返回值改变,通过dto方便扩展
了解更多详情请参考:
abp框架 - 数据传输对象
3,dto规范 (灵活应用)
abp建议命名输入/输出参数为:methodnameinput和methodnameoutput
并为每个应用服务方法定义单独的输入和输出dto(如果为每个方法的输入输出都定义一个dto,那将有一个庞大的dto类需要定义维护。一般通过定义一个公用的dto进行共用)
即使你的方法只接受/返回一个参数,也最好是创建一个dto类
一般会在对应实体的应用服务文件夹下新建dtos文件夹来管理dto类。
三、定义应用服务接口需要用到的dto
1, 先来看看taskdto的定义
namespace learningmpaabp.tasks.dtos{ /// <summary> /// a dto class that can be used in various application service methods when needed to send/receive task objects. /// </summary> public class taskdto : entitydto { public long? assignedpersonid { get; set; } public string assignedpersonname { get; set; } public string title { get; set; } public string description { get; set; } public datetime creationtime { get; set; } public taskstate state { get; set; } //this method is just used by the console application to list tasks public override string tostring() { return string.format( "[task id={0}, description={1}, creationtime={2}, assignedpersonname={3}, state={4}]", id, description, creationtime, assignedpersonid, (taskstate)state ); } } }
该taskdto直接继承自entitydto,entitydto是一个通用的实体只定义id属性的简单类。直接定义一个taskdto的目的是为了在多个应用服务方法中共用。
2, 下面来看看gettasksoutput的定义
就是直接共用了taskdto。
public class gettasksoutput { public list<taskdto> tasks { get; set; } }
3, 再来看看createtaskinput、updatetaskinput
public class createtaskinput { public int? assignedpersonid { get; set; } [required] public string description { get; set; } [required] public string title { get; set; } public taskstate state { get; set; } public override string tostring() { return string.format("[createtaskinput > assignedpersonid = {0}, description = {1}]", assignedpersonid, description); } }
/// <summary> /// this dto class is used to send needed data to <see cref="itaskappservice.updatetask"/> method. /// /// implements <see cref="icustomvalidate"/> for additional custom validation. /// </summary> public class updatetaskinput : icustomvalidate { [range(1, int32.maxvalue)] //data annotation attributes work as expected. public int id { get; set; } public int? assignedpersonid { get; set; } public taskstate? state { get; set; } [required] public string title { get; set; } [required] public string description { get; set; } //custom validation method. it's called by abp after data annotation validations. public void addvalidationerrors(customvalidationcontext context) { if (assignedpersonid == null && state == null) { context.results.add(new validationresult("both of assignedpersonid and state can not be null in order to update a task!", new[] { "assignedpersonid", "state" })); } } public override string tostring() { return string.format("[updatetaskinput > taskid = {0}, assignedpersonid = {1}, state = {2}]", id, assignedpersonid, state); } }
其中updatetaskinput实现了icustomvalidate接口,来实现自定义验证。了解dto验证可参考 abp框架 - 验证数据传输对象
##4, 最后来看一下gettasksinput的定义
其中包括两个属性用来进行过滤。
public class gettasksinput { public taskstate? state { get; set; } public int? assignedpersonid { get; set; } }
定义完dto,是不是脑袋有个疑问,我在用dto在展现层与应用服务层进行数据传输,但最终这些dto都需要转换为实体才能与数据库直接打交道啊。如果每个dto都要自己手动去转换成对应实体,这个工作量也是不可小觑啊。
聪明如你,你肯定会想肯定有什么方法来减少这个工作量。
四、使用automapper自动映射dto与实体
1,简要介绍automapper
开始之前,如果对automapper不是很了解,建议看下这篇文章automapper小结。
automapper的使用步骤,简单总结下:
创建映射规则(mapper.createmap<source, destination>();)
类型映射转换(mapper.map<source,destination>(sourcemodel))
在abp中有两种方式创建映射规则:
特性数据注解方式:
automapfrom、automapto 特性创建单向映射
automap 特性创建双向映射
代码创建映射规则:
mapper.createmap<source, destination>();
2,为task实体相关的dto定义映射规则
2.1,为createtasksinput、updatetaskinput定义映射规则
其中createtasksinput、updatetaskinput中的属性名与task实体的属性命名一致,且只需要从dto映射到实体,不需要反向映射。所以通过automapto创建单向映射即可。
[automapto(typeof(task))] //定义单向映射 public class createtaskinput { ... } [automapto(typeof(task))] //定义单向映射 public class updatetaskinput { ... }
2.2,为taskdto定义映射规则
taskdto与task实体的属性中,有一个属性名不匹配。taskdto中的assignedpersonname属性对应的是task实体中的assignedperson.fullname属性。针对这一属性映射,automapper没有这么智能需要我们告诉它怎么做;
var taskdtomapper = mapperconfig.createmap<task, taskdto>();
taskdtomapper.formember(dto => dto.assignedpersonname, map => map.mapfrom(m => m.assignedperson.fullname));
为taskdto与task创建完自定义映射规则后,我们需要思考,这段代码该放在什么地方呢?
四、创建统一入口注册automapper映射规则
如果在映射规则既有通过特性方式又有通过代码方式创建,这时就会容易混乱不便维护。
为了解决这个问题,统一采用代码创建映射规则的方式。并通过ioc容器注册所有的映射规则类,再循环调用注册方法。
1,定义抽象接口idtomapping
应用服务层根目录创建idtomapping接口,定义createmapping方法由映射规则类实现。
namespace learningmpaabp{ /// <summary> /// 实现该接口以进行映射规则创建 /// </summary> internal interface idtomapping { void createmapping(imapperconfigurationexpression mapperconfig); } }
2,为task实体相关dto创建映射类
namespace learningmpaabp.tasks{ public class taskdtomapping : idtomapping { public void createmapping(imapperconfigurationexpression mapperconfig) { //定义单向映射 mapperconfig.createmap<createtaskinput, task>(); mapperconfig.createmap<updatetaskinput, task>(); mapperconfig.createmap<taskdto, updatetaskinput>(); //自定义映射 var taskdtomapper = mapperconfig.createmap<task, taskdto>(); taskdtomapper.formember(dto => dto.assignedpersonname, map => map.mapfrom(m => m.assignedperson.fullname)); } } }
3,注册idtomapping依赖
在应用服务的模块中对idtomapping进行依赖注册,并解析以进行映射规则创建。
namespace learningmpaabp{ [dependson(typeof(learningmpaabpcoremodule), typeof(abpautomappermodule))] public class learningmpaabpapplicationmodule : abpmodule { public override void preinitialize() { configuration.modules.abpautomapper().configurators.add(mapper => { //add your custom automapper mappings here... }); } public override void initialize() { iocmanager.registerassemblybyconvention(assembly.getexecutingassembly()); //注册idtomapping iocmanager.ioccontainer.register( classes.fromassembly(assembly.getexecutingassembly()) .includenonpublictypes() .basedon<idtomapping>() .withservice.self() .withservice.defaultinterfaces() .lifestyletransient() ); //解析依赖,并进行映射规则创建 configuration.modules.abpautomapper().configurators.add(mapper => { var mappers = iocmanager.ioccontainer.resolveall<idtomapping>(); foreach (var dtomap in mappers) dtomap.createmapping(mapper); }); } } }
通过这种方式,我们只需要实现idtomappting进行映射规则定义。创建映射规则的动作就交给模块吧。
五、万事俱备,实现itaskappservice
认真读完以上内容,那么到这一步,就很简单了,业务只是简单的增删该查,实现起来就很简单了。可以自己尝试自行实现,再参考代码:
namespace learningmpaabp.tasks { /// <summary> /// implements <see cref="itaskappservice"/> to perform task related application functionality. /// /// inherits from <see cref="applicationservice"/>. /// <see cref="applicationservice"/> contains some basic functionality common for application services (such as logging and localization). /// </summary> public class taskappservice : learningmpaabpappservicebase, itaskappservice { //these members set in constructor using constructor injection. private readonly irepository<task> _taskrepository; private readonly irepository<person> _personrepository; /// <summary> ///in constructor, we can get needed classes/interfaces. ///they are sent here by dependency injection system automatically. /// </summary> public taskappservice(irepository<task> taskrepository, irepository<person> personrepository) { _taskrepository = taskrepository; _personrepository = personrepository; } public gettasksoutput gettasks(gettasksinput input) { var query = _taskrepository.getall(); if (input.assignedpersonid.hasvalue) { query = query.where(t => t.assignedpersonid == input.assignedpersonid.value); } if (input.state.hasvalue) { query = query.where(t => t.state == input.state.value); } //used automapper to automatically convert list<task> to list<taskdto>. return new gettasksoutput { tasks = mapper.map<list<taskdto>>(query.tolist()) }; } public async task<taskdto> gettaskbyidasync(int taskid) { //called specific getallwithpeople method of task repository. var task = await _taskrepository.getasync(taskid); //used automapper to automatically convert list<task> to list<taskdto>. return task.mapto<taskdto>(); } public taskdto gettaskbyid(int taskid) { var task = _taskrepository.get(taskid); return task.mapto<taskdto>(); } public void updatetask(updatetaskinput input) { //we can use logger, it's defined in applicationservice base class. logger.info("updating a task for input: " + input); //retrieving a task entity with given id using standard get method of repositories. var task = _taskrepository.get(input.id); //updating changed properties of the retrieved task entity. if (input.state.hasvalue) { task.state = input.state.value; } if (input.assignedpersonid.hasvalue) { task.assignedperson = _personrepository.load(input.assignedpersonid.value); } //we even do not call update method of the repository. //because an application service method is a 'unit of work' scope as default. //abp automatically saves all changes when a 'unit of work' scope ends (without any exception). } public int createtask(createtaskinput input) { //we can use logger, it's defined in applicationservice class. logger.info("creating a task for input: " + input); //creating a new task entity with given input's properties var task = new task { description = input.description, title = input.title, state = input.state, creationtime = clock.now }; if (input.assignedpersonid.hasvalue) { task.assignedperson = _personrepository.load(input.assignedpersonid.value); } //saving entity with standard insert method of repositories. return _taskrepository.insertandgetid(task); } public void deletetask(int taskid) { var task = _taskrepository.get(taskid); if (task != null) { _taskrepository.delete(task); } } } }
到此,此章节就告一段落。为了加深印象,请自行回答如下问题:
什么是应用服务层?
如何定义应用服务接口?
什么dto,如何定义dto?
dto如何与实体进行自动映射?
如何对映射规则统一创建?
以上就是abp入门系列(5)——创建应用服务的内容。
其它类似信息

推荐信息