[译][TinyERP:企业应用程序SPA]管理人员-第2部分
By robot-v1.0
本文链接 https://www.kyfws.com/applications/tinyerp-spa-for-enterprise-application-manage-st-2-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 11 分钟阅读 - 5043 个词 阅读量 0[译][TinyERP:企业应用程序SPA]管理人员-第2部分
原文地址:https://www.codeproject.com/Articles/1266415/TinyERP-SPA-for-Enterprise-Application-Manage-St-2
原文作者:tranthanhtu.vn
译文由本站 robot-v1.0 翻译
前言
[TinyERP: SPA for Enterprise Application]Manage Staffs - Part 2
[TinyERP:企业应用程序SPA]管理人员-第2部分
总览(Overview)
在本文中,我们为"管理人员"功能实现API端,并使用CQRS模式创建默认的人员.(In this article, we implement the API side for “Manage Staff” feature and create default Staff using CQRS pattern.)
对于API方面(For the API Side)
如"(As described in “) [TinyERP:企业应用程序的SPA]概述([TinyERP: SPA for Enterprise Application]Overview*) “,我们已经为API创建了项目.让我们在Visual Studio中将其打开,并添加一个名为”(”, we already created the project for API. Let’s open it in Visual Studio and add new “Class Library” project named “) TinyERP.HRM
“:(":*)
请记住删除新项目中不必要的CS文件,并添加”(Remember to delete unnecessary cs file in new project and add “) TinyERP.HRM
“变成”(” into “) TinyERP.Api
作为参考.(” as reference.)
添新(Add new)**StaffHandler.cs(StaffHandler.cs)**进入(into)TinyERP.HRM \ Api(TinyERP.HRM\Api):(:)
namespace TinyERP.HRM.Api
{
using Common.DI;
using Query;
using Search.Share;
using Share.Staff;
using System.Web.Http;
using TinyERP.Common.MVC;
using TinyERP.Common.MVC.Attributes;
[RoutePrefix("api/hrm/staffs")]
public class StaffHandler: BaseApiController
{
[Route("")]
[HttpGet()]
[ResponseWrapper()]
public ISearchResult<StaffListItem> GetStaffs() {
IStaffQuery query = IoC.Container.Resolve<IStaffQuery>();
return query.Search<StaffListItem>();
}
}
}
这很正常(This is the normal) ApiController
在(in) WebAPI
,有几点:(, there are some points:)
RoutePrefix
是”(is “)api/hrm/staffs(api/hrm/staffs)”.(".)Staff
是资源输出系统,因此所有请求都与(is the resource in out system, so all requests related to)Staff
会打电话给这个(will call to this)uri
.参见"(. See “) RESTful Web服务(RESTful Web Services) " 了解更多信息.(” for more information.)StaffHandler
继承自(was inherited from)BaseApiController
哪个定义(which defined)TinyERP.Common
.我们需要从nuget安装此软件包.有关更多信息,请参见(. We need to install this package from nuget. For more information, see) https://www.nuget.org/packages/TinyERP.Common(https://www.nuget.org/packages/TinyERP.Common)- 我们用(We use)
ResponseWrapper
大多数API方法的属性.(attribute for most API methods.) ISearchResult
用于我们要在某些条件下搜索数据的情况.例如,搜索人员使用名字,电子邮件.该接口在(was used in the case we want to search data with some conditions. For example, search staff using first name, email. This Interface was defined in)TinyERP.Search.Share
.请从nuget添加此软件包.看到(. Please add this package from nuget. See) TinyERP.Search.Share(TinyERP.Search.Share)IStaffQuery
仅用于读取数据库中的数据,因为我们为此功能使用CQRS模式.(was used to get data from read database only as we use CQRS pattern for this feature.)- IoC在(IoC was defined in)
TinyERP.Common
也.无需初始化此容器.(also. There is no need to initialize this container.) 让我们继续添加(Let’s continue adding)IStaffQuery.cs(IStaffQuery.cs):(:)
namespace TinyERP.HRM.Query
{
using TinyERP.Common.Data;
using TinyERP.Search.Share;
internal interface IStaffQuery : IBaseQueryRepository<TinyERP.HRM.Query.Entities.StaffSummary>
{
ISearchResult<TResult> Search<TResult>();
}
}
很简单,只需继承(It was simple, just inherit from) IBaseQueryRepository
.我们将从中获取数据(. We will get data from) StaffSummary
收集在(collection in) MongoDB
,这就是为什么我们需要在通用声明中指定此类.(, that is why we need to specify this class in generic declaration.)
和实施(And implementation for) IStaffQuery
:(:)
namespace TinyERP.HRM.Query
{
using Common.Data;
using TinyERP.HRM.Query.Entities;
using Search.Share;
using System.Linq;
using System.Collections.Generic;
using Common.Extensions;
internal class StaffQuery : BaseQueryRepository<StaffSummary>, IStaffQuery
{
public StaffQuery() : base() { }
public StaffQuery(IUnitOfWork uow) : base(uow.Context) { }
public ISearchResult<TResult> Search<TResult>()
{
IList<TResult> items = this.DbSet.AsQueryable().ToList().Cast<StaffSummary, TResult>();
ISearchResult<TResult> result = new SearchResult<TResult>(items, items.Count);
return result;
}
}
}
StaffQuery
也从(was also inherited from)BaseQueryRepository
.(.)- 有2个必需的构造函数,第一个用于读取,第二个用于在CQRS模式的读取站点上更新数据.(There are 2 required constructors, the first was used for reading and the second for updating data on read site of CQRS pattern.)
- 有可用(There is available)
DbSet
作为…的财产(as property of)BaseQueryRepository
,我们可以用来读取,更新或删除适当的数据.在这种情况下,我们可以使用(, we can use to read, update or delete appropriate data. In this case, we can use)DbSet
为了工作(for working on)StaffSummary
只要.只需将其转换为(only. Just need to convert this to)IQueryable
并使用LINQ来获取数据.(and using LINQ to get data.) - 演员:这是从中转换的扩展名(Cast: This is extension to convert from)
A
上课(class to)B
类.在此示例中,它将转换来自(class. In this sample, It will convert collection from)StaffSummary
至(to)StaffListItem
类型.(type.) 我们还需要映射(We also need to map)IStaffQuery
至(to)StaffQuery
使用(using)IBootstrapper<ITaskArgument> interface
:(:)
namespace TinyERP.HRM.Query
{
using TinyERP.Common.DI;
using TinyERP.Common.Tasks;
public class Bootstrap:BaseTask<ITaskArgument>, IBootstrapper<ITaskArgument>
{
public Bootstrap():base(Common.ApplicationType.All){}
public override void Execute(ITaskArgument context)
{
if (!this.IsValid(context.Type)) { return; }
IBaseContainer container = context.Context as IBaseContainer;
container.RegisterTransient<IStaffQuery, StaffQuery>();
}
}
}
我建议对于大多数接口,我们应该使用瞬态.这将减少运行时的内存量.(I suggest that we should use transient for most interfaces. This will reduce the amount of memory at runtime.)
让我们定义(Let’s define) StaffSummary
类,这是mongodb服务器中的集合:(class, this is a collection in mongodb server:)
namespace TinyERP.HRM.Query.Entities
{
using Common.MVC.Attributes;
using Context;
using System;
using TinyERP.Common;
[DbContext(Use = typeof(IHRMQueryContext))]
internal class StaffSummary: AggregateSummaryEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Department { get; set; }
public StaffSummary(Guid aggregateId):base(aggregateId){}
}
}
有一些有趣的观点:(There are some interesting points:)
DbContext
属性:描述我们要使用的数据库上下文.这对于这种情况很有用,因为我们在单个数据库上有很多实体(表),所以我们可以将其分解为多个较小的数据库.我们将在"(attribute: Describe which database context we want to use. This is useful for the case, we have a lot entities (tables) on single database, so we can break this into multiple smaller databases. We will mention this again in “) 扩展您的存储库(Scale your repository) “文章.(” article.)StaffSummary
被视为"员工域"的聚合根.这就是为什么它需要继承(was considered as aggregate root for “staff domain”. That is why it needs to inherit from)AggregateSummaryEntity
(从((from) TinyERP.Common(TinyERP.Common) 包).(package).)- 聚合根必须具有带有(The aggregate root must have the constructor with the)
GUID
值.这是写数据库上适当对象的ID.(value. This is the ID of appropriated object on write database.) 和(And)IHRMQueryContext.cs(IHRMQueryContext.cs):(:)
namespace TinyERP.HRM.Context
{
using TinyERP.Common.Data;
public interface IHRMQueryContext:IDbContext
{
}
}
在这个介面中,我们只需要继承(In this interface, we only need to inherit from) IDbContext
.(.)
好的,现在,我们可以从读取的数据库中获取人员列表,然后返回到客户端.(OK, for now, we can get the list of staff from read database and return back to client side.)
最后,我们需要为(Finally, we need to config the connection string for) IHRMQueryContext
在(in)TinyERP.Api/config/configuration.debug.config(TinyERP.Api/config/configuration.debug.config).在"聚合"部分中,添加:(. In aggregates section, add:)
<add name="TinyERP.HRM.Context.IHRMQueryContext"
repoType="MongoDb" connectionStringName="DefaultMongoDb"></add>
并将其添加到数据库部分:(and add this into databases section:)
<add
name="DefaultMongoDb"
database="TinyERP"
server="localhost"
port="27017"
userName=""
password=""
ssl="false"
dbType="MongoDb"
default="true"
></add>
通过以上配置,我们告诉系统,(With the above configuration, we tell with the system that,) IHRMQueryContext
将连接到MongoDB并使用(will connect to MongoDB and using) DefaultMongoDb
连接(connection) string
如下所述.(as described below.)
跑吧(Let’s run) TinyERP.Api
并致电(and call the) GetStaffs
方法,结果如下:(method, the result is as below:)
让我解释一下:(Let me explain a little:)
status
:这确定请求是成功还是失败.我们通常使用200(确定),400(错误请求),500(InternalServerError).(: This determines if the request was success or fail. We usually use 200 (OK), 400 (Bad Request), 500 (InternalServerError).)Errors
:这将包含验证错误列表,例如:登录请求中的"无效的用户名或密码”.(: This will contain the list of validation errors, for example: “invalid user name or password” in login request.)Data
:如果状态为200,这是服务器的响应.(: This is the response from server if the status is 200.) 目前,我们收到(For now, we receive empty in)data
属性,因为mongodb服务器中没有数据.(property as there is no data in mongodb server.)
我认为,目前,我们应该创建一些人员作为初始化数据.(I think, for now, we should create some staffs as initialized data.)
新增职员(Add New Staff)
现在,我们将创建(Now, we will create) CreateDefaultStaff
在"(in “)TinyERP.HRM \ Share \ Tasks(TinyERP.HRM\Share\Tasks)“文件夹:(” folder:)
namespace TinyERP.HRM.Share.Task
{
using Command.Staff;
using Common.Command;
using TinyERP.Common.Tasks;
public class CreateDefaultStaff: BaseTask<ITaskArgument>,
TinyERP.Common.Tasks.IApplicationReadyTask<ITaskArgument>
{
public CreateDefaultStaff():base(Common.ApplicationType.All){}
public override void Execute(ITaskArgument context)
{
if (!this.IsValid(context.Type)) { return; }
CreateStaffRequest request =
new CreateStaffRequest("Tu", "Tran", "contact@tranthanhtu.vn");
ICommandHandlerStrategy commandHandler =
CommandHandlerStrategyFactory.Create<TinyERP.HRM.Aggregate.Staff>();
CreateStaffResponse response =
commandHandler.Execute<CreateStaffRequest, CreateStaffResponse>(request);
this.Logger.Info("New staff (id: {0}) was created", response.Id);
}
}
}
- 创建起来很简单(It was simple to create)
CreateStaffRequest
并打电话(and call)Execute
方法.(method.)System
会将请求重定向到必要的位置.(will redirect this request to the necessary location.) - 在应用程序生命周期中,我们可以注入自定义任务的许多阶段.完成所有必要的配置后,将调用此任务.(There are many phases in the life-cyle of the application which we can inject custom task. When all necessary configurations complete, this task will be called.)
内容(Content of)
CreateStaffRequest
和(and)CreateStaffResponse
如下:(as below:)
namespace TinyERP.HRM.Command.Staff
{
using TinyERP.Common.Command;
public class CreateStaffRequest: IBaseCommand
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public CreateStaffRequest(string firstName, string lastName, string email)
{
this.FirstName = firstName;
this.LastName = lastName;
this.Email = email;
}
}
}
namespace TinyERP.HRM.Command.Staff
{
using System;
class CreateStaffResponse
{
public Guid Id { get; set; }
}
}
我们可以看到这很简单.使用3个字段创建请求:名字,姓氏和电子邮件,并接收新创建人员的ID.(We can see that it was simple. Create request with 3 fields: first name, last name and email and receive back the Id of newly created staff.)
我们需要为此请求注册处理程序,创建命令文件夹并添加此类:(We need to register the handler for this request, Create command folder and add this class:)
using TinyERP.Common.Command;
using TinyERP.Common.DI;
using TinyERP.Common.Tasks;
using TinyERP.HRM.Command.Staff;
namespace TinyERP.HRM.Command
{
public class Bootstrap: BaseTask<ITaskArgument>, IBootstrapper<ITaskArgument>
{
public Bootstrap():base(Common.ApplicationType.All){}
public override void Execute(ITaskArgument arg)
{
if (!this.IsValid(arg.Type)) { return; }
IBaseContainer container = arg.Context as IBaseContainer;
container.RegisterTransient<IBaseCommandHandler
<CreateStaffRequest, CreateStaffResponse>, StaffCommandHandler>();
}
}
}
这意味着(This means that) CreateStaffRequest
请求将被重定向到(request will be redirected to) StaffCommandHandler
类:(class:)
namespace TinyERP.HRM.Command
{
using System;
using TinyERP.Common.Command;
using Staff;
using Common.Helpers;
using Common.Validation;
using Common.Data;
using Repository;
using Common.DI;
internal class StaffCommandHandler : BaseCommandHandler, IStaffCommandHandler
{
public CreateStaffResponse Handle(CreateStaffRequest command)
{
this.Validate(command);
using (IUnitOfWork uow = this.CreateUnitOfWork<TinyERP.HRM.Aggregate.Staff>()) {
TinyERP.HRM.Aggregate.Staff staff = new Aggregate.Staff();
staff.UpdateBasicInfo(command);
IStaffRepository repository = IoC.Container.Resolve<IStaffRepository>(uow);
repository.Add(staff);
uow.Commit();
staff.PublishEvents();
return ObjectHelper.Cast<CreateStaffResponse>(staff);
}
}
private void Validate(CreateStaffRequest command)
{
IValidationException validator = ValidationHelper.Validate(command);
// and other business validations here, such as: unit first + last name, unit email, ....
validator.ThrowIfError();
}
}
}
我认为代码很简单,只需定义(I think the code was straight-forward, just define) Handle
声明的方法(method which was declared by) IStaffCommandHandler
:(:)
namespace TinyERP.HRM.Command
{
using TinyERP.Common.Command;
using TinyERP.HRM.Command.Staff;
internal interface IStaffCommandHandler:
IBaseCommandHandler<CreateStaffRequest, CreateStaffResponse>
{
}
}
我们应该采取所有适当的方法(We should all appropriate methods of) aggregate
对象以执行必要的操作(例如:在这种情况下,更新名字).(object to perform necessary action (such as: update first name in this case).)
对于每次更改(For each change in) aggregate
,将引发新事件.然后,CQRS模式的读取端将订阅这些事件并适当地更新数据.(, the new event will be raised. Then the read side of CQRS pattern will subscribe those events and update data appropriately.)
因此对于(So, for)Staff.cs(Staff.cs):(:)
namespace TinyERP.HRM.Aggregate
{
using TinyERP.Common.Aggregate;
using Command.Staff;
using Event;
using Common.MVC.Attributes;
using Context;
[DbContext(Use = typeof(IHRMContext))]
internal class Staff: BaseAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Staff()
{
this.AddEvent(new OnStaffCreated(this.Id));
}
internal void UpdateBasicInfo(CreateStaffRequest command)
{
this.FirstName = command.FirstName;
this.LastName = command.LastName;
this.Email = command.Email;
this.AddEvent(new OnStaffBasicInforChanged
(this.Id, this.FirstName, this.LastName, this.Email));
}
}
}
和(and)StaffEventhandler.cs(StaffEventhandler.cs):(:)
namespace TinyERP.HRM.Event
{
using System;
using Common.Data;
using Common.DI;
using Query;
using Query.Entities;
using TinyERP.Common.Event;
internal class StaffEventHandler : BaseEventHandler, IStaffEventHandler
{
public void Execute(OnStaffBasicInforChanged ev)
{
using (IUnitOfWork uow = this.CreateUnitOfWork<StaffSummary>())
{
IStaffQuery query = IoC.Container.Resolve<IStaffQuery>(uow);
StaffSummary staff = query.GetByAggregateId(ev.StaffId.ToString());
staff.FirstName = ev.FirstName;
staff.LastName = ev.LastName;
staff.Email = ev.Email;
query.Update(staff);
uow.Commit();
}
}
public void Execute(OnStaffCreated ev)
{
using (IUnitOfWork uow = this.CreateUnitOfWork<StaffSummary>())
{
StaffSummary summary = new StaffSummary(ev.StaffId);
IStaffQuery query = IoC.Container.Resolve<IStaffQuery>(uow);
query.Add(summary);
uow.Commit();
}
}
}
}
在(In) StaffEventHandler
,我们会收到更改(由(, we receive changes (raised by) StaffCommandHandler
)并更新到读取数据库(在本例中为mongodb).() and update into read database (it was mongodb in this case).)
我们还需要配置(We also need to config for) IHRMContext
如同(similar to) IHRMQueryContext
.(.)
在汇总部分(在(In aggregates section (in)config/configuration.debug.config(config/configuration.debug.config)),然后添加:(), add:)
<add name="TinyERP.HRM.Context.IHRMContext" repoType="MSSQL"
connectionStringName="DefaultMSSQL"></add>
并将其添加到数据库部分:(and add this into databases section:)
<add
name="DefaultMSSQL"
database="TinyERP"
server=".\SqlExpress"
port="0"
userName="sa"
password="123456"
dbType="MSSQL"
></add>
让我们尝试编译并再次运行该应用程序,您可以看到MSSQL和Mongodb中都添加了新记录:(Let try to compile and run the app again, you can see there is new record added into both MSSQL and Mongodb:)
好吧,所以叫”(Ok, So, call “)**api/hrm/staffs(api/hrm/staffs)**再次:(” again:)
当前HRM项目的结构为:(And the structure for current HRM project is:)
让我们转到最后一步以完成本文.(Let’s go to the last step to finish this article.)
打开(Open)**staffService.ts(staffService.ts)**在客户端上并更新(on client and update) uri
至(to) api
:(:)
export class StaffService extends BaseService implements IStaffService{
public getStaffs():Promise{
let uri="http://localhost:56622/api/hrm/staffs";
let iconnector: IConnector = window.ioc.resolve(IoCNames.IConnector);
return iconnector.get(uri);
}
}
编译并再次运行客户端.我们可以看到(Compile and run the client again. We can see the list of) staffs
在用户界面上显示.(were displayed on UI.)
有关"管理人员"的流程的概述如下:(An overview about the flow for “Manage Staff” is as below:)
到目前为止,我们可以:(Until now, we can:)
- 使用创建新员工(Create new staff using)
IApplicationReadyTask interface
.(.) - 创建并发送(Create and send)
CreateStaffRequest
.(.) - 使用以下命令注册命令处理程序/事件处理程序(Register command handler/ event handler using)
IBootstrapper interface
.(.) - 定义(Define)
StaffCommandHandler
和(and)IStaffCommandHandler
处理(to handle)CreateStaffRequest
.(.) - 定义(Define)
StaffEventHandler
和(and)IStaffEventHandler
处理"员工域"的适当事件.(to handle appropriated event for “staff domain”.) - 取得清单(Get list of)
Staffs
使用(using)IStaffQuery
.(.) 我们会继续就"(There are many questions we continue to clarify on “) 修订管理人员(Revise Manage Staff) “文章稍后.(” article later.)
有关本部分中的参考源代码,请查看(For the reference source code in this part, please have a look at) https://github.com/tranthanhtu0vn/TinyERP(https://github.com/tranthanhtu0vn/TinyERP) (科:((branch:)功能/管理人员(feature/manage_staff)).().)
系列文章:(Other articles in series:)
- 总览(Overview)
- “管理人员"样本(“Manage Staffs” sample)
- “管理员工"第2部分(API)(“Manage Staffs” part 2(API))
- 跨模块/域通信(Cross modules/ domains communication)
- 处理错误/验证(Handle Error/ Validation) 感谢您的阅读.(Thank you for reading.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# COM LINQ MongoDB entity-framework Visual-Studio CQRS WebAPI2 class IOC 新闻 翻译