使用 Serenity 服务行为

如果想把多租户系统扩展到 Northwind 数据库中的其他表,我们会重复角色所做的相同步骤。虽然看起来没那么难,但是有太多的手工工作。

Serenity 提供服务行为系统,它可以允许我们拦截添加、更新、检索、列表、删除的操作处理并向其添加用户自定义代码。

在这些处理中有一些操作(如像获取日志、唯一约束验证等)已经使用服务行为实现了。

行为(Behaviors)可能被所有的行(rows)激活,或被基于某些规则(如特定的特性或接口)的行激活。例如,含 [CaptureLog] 特性的行激活 CaptureLogBehavior。

我们首先定义一个将触发新行为的接口 IMultiTenantRow。把此类放在 TenantRow.cs 旁边的 IMultiTenantRow.cs 中:

using Serenity.Data;

namespace MultiTenancy
{
    public interface IMultiTenantRow
    {
        Int32Field TenantIdField { get; }
    }
}

然后在旁边的 MultiTenantBehavior.cs 文件添加行为:

using MultiTenancy.Administration;
using Serenity;
using Serenity.Data;
using Serenity.Services;

namespace MultiTenancy
{
    public class MultiTenantBehavior : IImplicitBehavior,
        ISaveBehavior, IDeleteBehavior,
        IListBehavior, IRetrieveBehavior
    {
        private Int32Field fldTenantId;

        public bool ActivateFor(Row row)
        {
            var mt = row as IMultiTenantRow;
            if (mt == null)
                return false;

            fldTenantId = mt.TenantIdField;
            return true;
        }

        public void OnPrepareQuery(IRetrieveRequestHandler handler, 
            SqlQuery query)
        {
            var user = (UserDefinition)Authorization.UserDefinition;
            if (!Authorization.HasPermission(PermissionKeys.Tenants))
                query.Where(fldTenantId == user.TenantId);
        }

        public void OnPrepareQuery(IListRequestHandler handler, 
            SqlQuery query)
        {
            var user = (UserDefinition)Authorization.UserDefinition;
            if (!Authorization.HasPermission(PermissionKeys.Tenants))
                query.Where(fldTenantId == user.TenantId);
        }

        public void OnSetInternalFields(ISaveRequestHandler handler)
        {
            if (handler.IsCreate)
                fldTenantId[handler.Row] =
                    ((UserDefinition)Authorization
                        .UserDefinition).TenantId;
        }

        public void OnValidateRequest(IDeleteRequestHandler handler)
        {
            var user = (UserDefinition)Authorization.UserDefinition;
            if (fldTenantId[handler.Row] != user.TenantId)
                Authorization.ValidatePermission(
                    PermissionKeys.Tenants);
        }

        public void OnAfterDelete(IDeleteRequestHandler handler) { }
        public void OnAfterExecuteQuery(IRetrieveRequestHandler handler) { }
        public void OnAfterExecuteQuery(IListRequestHandler handler) { }
        public void OnAfterSave(ISaveRequestHandler handler) { }
        public void OnApplyFilters(IListRequestHandler handler, SqlQuery query) { }
        public void OnAudit(IDeleteRequestHandler handler) { }
        public void OnAudit(ISaveRequestHandler handler) { }
        public void OnBeforeDelete(IDeleteRequestHandler handler) { }
        public void OnBeforeExecuteQuery(IRetrieveRequestHandler handler) { }
        public void OnBeforeExecuteQuery(IListRequestHandler handler) { }
        public void OnBeforeSave(ISaveRequestHandler handler) { }       
        public void OnPrepareQuery(IDeleteRequestHandler handler, SqlQuery query) { }
        public void OnPrepareQuery(ISaveRequestHandler handler, SqlQuery query) { }
        public void OnReturn(IDeleteRequestHandler handler) { }
        public void OnReturn(IRetrieveRequestHandler handler) { }
        public void OnReturn(IListRequestHandler handler) { }
        public void OnReturn(ISaveRequestHandler handler) { }
        public void OnValidateRequest(IRetrieveRequestHandler handler) { }
        public void OnValidateRequest(IListRequestHandler handler) { }
        public void OnValidateRequest(ISaveRequestHandler handler) { }
    }
}

行为类实现 IImplicitBehavior 接口来决定是否应该被指定的行类型(row type)激活。

它是通过实现 ActivateFor 方法做到的,该方法由请求处理(request handlers)调用。

在该方法中,我们检查行类型(row type)是否实现 IMultiTenantRow 接口,如果没有,则返回 false。

然后,我们得到一个 TenantIdField 的私有引用,以便之后在其他方法中使用。

ActivateFor 在每个处理类型(handler type)和行(row)中只被调用一次。如果该方法返回 true,行为实例出于性能考虑而被缓存,并且被该行(row)和处理类型(handler type)重用。

因此,由于每个实例都被所有的请求共享,所以你在其他方法中所写的代码必须是线程安全的。

一个行为通过实现 IRetrieveBehavior, IListBehavior, ISaveBehavior, 或 IDeleteBehavior 接口,可以拦截一个或多个检索列表保存删除 处理。

在这里,我们需要拦截所有这些服务调用,因此我们实现所有的接口。

我们只实现相关的方法,其他的方法保留为空。

我们这里实现的方法,对应于上一章节在 RoleRepository.cs 重写的方法。它们所包含的代码几乎是相同的,但我们这里需要更加通用,因为该行为将为所有实现 IMultiTenantRow 接口的行类型工作。

使用行为重新实现 RoleRepository

现在还原我们在 RoleRepository.cs 做的所有修改:

private class MySaveHandler : SaveRequestHandler<MyRow> { }
private class MyDeleteHandler : DeleteRequestHandler<MyRow> { }
private class MyRetrieveHandler : RetrieveRequestHandler<MyRow> { }
private class MyListHandler : ListRequestHandler<MyRow> { }

并且在 RoleRow 添加 IMultiTenantRow 接口:

namespace MultiTenancy.Administration.Entities
{
    //...
    public sealed class RoleRow : Row, IIdRow, INameRow, IMultiTenantRow
    {
        //...
        public Int32Field TenantIdField
        {
            get { return Fields.TenantId; }
        }
        //...
    }
}

使用更少的代码得到相同的结果。声明式编程几乎总是更好的选择。

results matching ""

    No results matching ""