如何处理从Controller的Initialize方法引发的异常?

时间:2018-11-01 18:19:57

标签: c# asp.net-mvc controller multi-tenant

问题:

如何处理从Controller的Initialize方法引发的异常?

背景故事:

我们有用于使用单个数据库的.NET MVC应用程序。我们有许多控制器在构造函数中创建数据库上下文作为成员,然后在操作中使用它。连接字符串存储在Web.config中。新的要求是我们要支持多个客户端,每个客户端在同一应用程序实例(multi-tenant)中具有单独的数据库。我们不希望控制器知道多个数据库的存在。我们有目录数据库,可以从该数据库获取给定的客户端连接字符串。最初的方法是为覆盖Controller.Initialize的控制器创建通用基础,因为这是我们首先可以获取user identity并向目录数据库查询客户端连接字符串并初始化数据库上下文的方法。直到我们发现需要不将用户连接到任何特定数据库为止,它的工作效果都很好。然后的想法是在Initialize中引发异常,并在异常过滤器中捕获该异常,以将用户重定向到页面,通知此页面功能需要分配给数据库。不幸的是,初始化不是动作,并且从其引发的异常不适用于过滤器。

1 个答案:

答案 0 :(得分:0)

根据您的问题,我了解到您正在为要构建的应用程序启用承租人数据库模型。在这种情况下,您应该有一家从事以下工作的工厂

  • 租户解析(通过URL或基于任何其他输入参数)
  • 具有一个分片管理器,该管理器通过租户标识符查找连接字符串,然后使用该分管器联系该租户的正确数据库。

简而言之,您应该拥有类似以下的内容

public interface ITenantShardResolver
    {
        /// <summary>
        /// Gets the tenant specific shard based connection from the metadata
        /// </summary>
        /// <param name="tenantId">The tenant identifier</param>
        /// <param name="connectionStringName">The Connection string name</param>
        /// <returns>
        /// The DbConnection for the specific shard per tenant
        /// <see cref="DbConnection"/> 
        /// </returns>
        DbConnection GetConnection(Guid tenantId, string connectionStringName);
    }

上面是一个通用接口,可用于基于已建立的租户上下文获取连接字符串。

基本数据库上下文如下所示

public abstract class EntitiesContext : DbContext, IEntitiesContext
    {
        /// <summary>
        /// Constructs a new context instance using conventions to create the name of
        /// the database to which a connection will be made. The by-convention name is
        /// the full name (namespace + class name) of the derived context class.  See
        /// the class remarks for how this is used to create a connection. 
        /// </summary>
        protected EntitiesContext() : base()
        {
        }

        /// <summary>
        /// Initializes the entity context based on the established user context and the tenant shard map resolver
        /// </summary>
        /// <param name="userContext">The user context</param>
        /// <param name="shardResolver">The Tenant Shard map resolver</param>
        /// <param name="nameorConnectionString">The name or the connection string for the entity</param>
        protected EntitiesContext(MultiTenancy.Core.ProviderContracts.IUserContextDataProvider userContext, ITenantShardResolver shardResolver, string nameorConnectionString)
            : base(shardResolver.GetConnection(userContext.TenantId, nameorConnectionString), true)
        {

        }
}

示例上下文如下所示

public class AccommodationEntities : EntitiesContext
    {
        public AccommodationEntities(IUserContextDataProvider userContextProvider, ITenantShardResolver shardResolver)
            : base(userContextProvider, shardResolver, "AccommodationEntities")
        { }

        // NOTE: You have the same constructors as the DbContext here. E.g:
        public AccommodationEntities() : base("AccommodationEntities") { }

        public IDbSet<Country> Countries { get; set; }
        public IDbSet<Resort> Resorts { get; set; }
        public IDbSet<Hotel> Hotels { get; set; }
    }

可以与上述上下文对话的基本服务看起来像下面的

public abstract class MultiTenantServices<TEntity, TId>
    where TEntity : class, IMultiTenantEntity<TId>
    where TId : IComparable
{
    private readonly IMultiTenantRepository<TEntity, TId> _repository;

    /// <summary>
    /// Initializes a new instance of the <see cref="MultiTenantServices{TEntity, TId}"/> class.
    /// </summary>
    /// <param name="repository">The repository.</param>
    protected MultiTenantServices(IMultiTenantRepository<TEntity, TId> repository)
    {
        _repository = repository;
    }

示例实体服务如下所示,

public class CountryService : MultiTenantServices<Country, int>
    {
        IMultiTenantRepository<Country, int> _repository = null;

        public CountryService(IMultiTenantRepository<Country, int> repository) : base(repository)
        {
            _repository = repository;
        }

上面的代码段说明了一种经过良好测试的好方法,也是组织/构建用于多租户应用的好方法。

HTH