0

所以我有一个典型的三层应用程序分层如下

DAL -> 存储库 -> 业务 -> Web.UI/API

我一直在阅读这篇关于通过模块集中它们来注册依赖项的文章。

Web 层只有一个对 Business 的引用,它只有一个对 Repo 的引用,它只对最低 DAL 层有一个引用。在此拓扑中,由于 UI/API 层对存储库一无所知,也没有对其的引用,因此我无法在 UI/API 层的存储库中注册模块。同样,我无法在业务层中注册 DAL 中存在的模块。我想要做的是在最顶层开始注册过程,然后在后续层中引发注册的级联效应。

通常,这看起来像每一层都暴露一个 RegisterAllModules 方法,并以某种方式从它下面的层触发 RegisterAllModules 方法。有没有做过这样的事情?还是有其他方法可以做到这一点?在这一点上,我不知道我是否应该像上面提到的那样推出自己的逻辑,因为我不知道是否有记录的方式来做这样的事情。关于如何在这里最好地前进的想法是我正在寻找的。

谢谢。

4

1 回答 1

2

嗯...我不知道接下来的反应是否正确,但我将尝试为您提供适合您确切要求的解决方案的工具。

  • 您是否查看过 json/xml 模块配置?您不需要通过交叉引用知道程序集,您只需要知道 app.config(或 web.config)中的程序集的名称。例如:您可以在 Repo 程序集中为 Repositories 注册一个模块,在 Business.dll 中为 Business services 注册一个模块。这完全消除了交叉引用各种程序集的需要(对于模块扫描,您仍然需要方法调用的引用,但无论如何这是意料之中的)。有关详细信息,请参见此处:http: //docs.autofac.org/en/latest/configuration/xml.html#configuring-with-microsoft-configuration
  • 如果您想强制从(例如)UI 到 Repo 不进行任何调用,您可以利用“Instance Per Matching Lifetime Scope”功能(参见http://docs.autofac.org/en/latest/lifetime/instance-scope .html#instance-per-matching-lifetime-scope)。您可以使用该注册方法来强制执行工作单元方法。例如:一个存储库只能在“存储库”LifetimeScope 中解析,并且只有业务组件打开标记为“存储库”的范围。
  • 标记范围的另一种方法是使用“Instance per Owned<>”模式。这样,每个业务服务都需要一个 Owned<Repository>。就像是:

    var builder = new ContainerBuilder(); builder.RegisterType(); builder.RegisterType().InstancePerOwned();

AFAICT,正确的方法是通过 Json/Xml 配置引用的模块注册组件,并且每个模块应针对特定的 LifetimeScopes。当你一个类调用底层时,它应该打开一个新的 LifetimeScope("underlying layer")。

如果您需要有关实施策略的建议,我将进一步详细说明。

最好的,

阿尔贝托·基耶萨

编辑:

我不知道“组成根”的含义。嗯,谢谢你的信息!我喜欢简单的配置文件(无论是 .config 文件还是单独的 .json 或 .xml 文件),因为我觉得要导入的模块列表通过列表比通过类更简单。但这是意见。没有意见的是,您可以以简单且经过测试的方式从“组合根”程序集未引用的程序集中导入模块。

因此,我会为每个组件注册选择模块,但为模块注册选择文本配置文件。YMMV。

现在,让我向您展示一个我在许多实际项目中使用的工作单元模式的示例。

在我们的架构中,我们大量使用服务层,它负责打开与数据库的连接并在完成后处理它们等。它比你所追求的更简单(我更喜欢浅而不是深),但是概念是一样的。

如果您“脱离”了服务层(例如,在 MVC 控制器中或 UI 中),则需要一个 ServiceHandle 才能访问服务层。ServiceHandle 是唯一了解 Autofac 并负责服务解析、调用和处置的类。

对服务层的访问是这样完成的:

  • 非服务类可以只需要一个 ServiceHandle
  • 调用是通过 _serviceHandle.Invoke(Func) 完成的
  • Autofac 通过构造函数注入注入准备使用的句柄。

这是通过使用 BeginLifetimeScope(tag) 方法完成的,并以这种方式注册服务(在模块中):

// register every service except for ServiceBase
Builder.RegisterAssemblyTypes(_modelAssemblies)
    .Where(t => typeof(IService).IsAssignableFrom(t) && (t != typeof(ServiceBase)))
    .InstancePerDependency();

// register generic ServiceHandle
Builder.RegisterGeneric(typeof(ServiceHandle<>))
    .AsSelf()
    .AsImplementedInterfaces()
    .InstancePerDependency();

并将每个共享资源注册为 InstancePerMatchingLifetimeScope("service")

因此,一个示例调用将是:

... in the constructor:
public YourUiClass(ServiceHandle<MyServiceType> myserviceHandle)
{
  this._myserviceHandle = myserviceHandle;
}

... in order to invoke the service:
var result = _myserviceHandle.Invoke(s => s.myServiceMethod(parameter));

这是 ServiceHandle 实现:

/// <summary>
/// Provides a managed interface to access Model Services
/// </summary>
/// <typeparam name="TServiceType">The Type of the parameter to be managed</typeparam>
public class ServiceHandle<TServiceType> : IServiceHandle<TServiceType> where TServiceType : IService
{
    static private readonly ILog Log = LogManager.GetLogger(typeof(ServiceHandle<TServiceType>));

    private readonly ILifetimeScope _scope;

    /// <summary>
    /// True if there where Exceptions caught during the last Invoke execution.
    /// </summary>
    public bool ErrorCaught { get; private set; }

    /// <summary>
    /// List of the errors caught during execution
    /// </summary>
    public List<String> ErrorsCaught { get; private set; }

    /// <summary>
    /// Contains the exception that was thrown during the
    /// last Invoke execution.
    /// </summary>
    public Exception ExceptionCaught { get; private set; }

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="scope">The current Autofac scope</param>
    public ServiceHandle(ILifetimeScope scope)
    {
        if (scope == null)
            throw new ArgumentNullException("scope");

        _scope = scope;

        ErrorsCaught = new List<String>();
    }

    /// <summary>
    /// Invoke a method to be performed using a 
    /// service instance provided by the ServiceHandle
    /// </summary>
    /// <param name="command">
    /// Void returning action to be performed
    /// </param>
    /// <remarks>
    /// The implementation simply wraps the Action into
    /// a Func returning an Int32; the returned value
    /// will be discarded.
    /// </remarks>
    public void Invoke(Action<TServiceType> command)
    {
        Invoke(s =>
        {
            command(s);
            return 0;
        });
    }

    /// <summary>
    /// Invoke a method to be performed using a 
    /// service instance provided by the ServiceHandle
    /// </summary>
    /// <typeparam name="T">Type of the data to be returned</typeparam>
    /// <param name="command">Action to be performed. Returns T.</param>
    /// <returns>A generically typed T, returned by the provided function.</returns>
    public T Invoke<T>(Func<TServiceType, T> command)
    {
        ErrorCaught = false;
        ErrorsCaught = new List<string>();
        ExceptionCaught = null;

        T retVal;

        try
        {
            using (var serviceScope = GetServiceScope())
            using (var service = serviceScope.Resolve<TServiceType>())
            {
                try
                {
                    retVal = command(service);

                    service.CommitSessionScope();
                }
                catch (RollbackException rollbackEx)
                {
                    retVal = default(T);

                    if (System.Web.HttpContext.Current != null)
                        ErrorSignal.FromCurrentContext().Raise(rollbackEx);

                    Log.InfoFormat(rollbackEx.Message);

                    ErrorCaught = true;
                    ErrorsCaught.AddRange(rollbackEx.ErrorMessages);
                    ExceptionCaught = rollbackEx;

                    DoRollback(service, rollbackEx.ErrorMessages, rollbackEx);
                }
                catch (Exception genericEx)
                {
                    if (service != null)
                    {
                        DoRollback(service, new List<String>() { genericEx.Message }, genericEx);
                    }

                    throw;
                }
            }
        }
        catch (Exception ex)
        {
            if (System.Web.HttpContext.Current != null)
                ErrorSignal.FromCurrentContext().Raise(ex);

            var msg = (Log.IsDebugEnabled) ?
                String.Format("There was an error executing service invocation:\r\n{0}\r\nAt: {1}", ex.Message, ex.StackTrace) :
                String.Format("There was an error executing service invocation:\r\n{0}", ex.Message);

            ErrorCaught = true;
            ErrorsCaught.Add(ex.Message);
            ExceptionCaught = ex;

            Log.ErrorFormat(msg);

            retVal = default(T);
        }

        return retVal;
    }

    /// <summary>
    /// Performs a rollback on the provided service instance
    /// and records exception data for error retrieval.
    /// </summary>
    /// <param name="service">The Service instance whose session will be rolled back.</param>
    /// <param name="errorMessages">A List of error messages.</param>
    /// <param name="ex"></param>
    private void DoRollback(TServiceType service, List<string> errorMessages, Exception ex)
    {
        var t = new Task<string>
        service.RollbackSessionScope();
    }

    /// <summary>
    /// Creates a Service Scope overriding Session resolution:
    /// all the service instances share the same Session object.
    /// </summary>
    /// <returns></returns>
    private ILifetimeScope GetServiceScope()
    {
        return _scope.BeginLifetimeScope("service");
    }
}

希望能帮助到你!

于 2016-01-13T16:52:02.687 回答