一、解释下应用服务层
应用服务用于将领域(业务)逻辑暴露给展现层。展现层通过传入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)——创建应用服务的内容。