简体   繁体   English

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

[英]How to handle exception thrown from Controller's Initialize method?

Question: 题:

How to handle exception thrown from Controller's Initialize method? 如何处理从Controller的Initialize方法引发的异常?

Background story: 背景故事:

We have .NET MVC application that used to work with single database. 我们拥有用于单个数据库的.NET MVC应用程序。 We have many controllers that create database context in constructor as member and than use it in actions. 我们有许多控制器在构造函数中创建数据库上下文作为成员,然后在操作中使用它。 Connection string was stored in Web.config. 连接字符串存储在Web.config中。 New requirement is that we want to support multiple clients each with separate database in same application instance ( multi-tenant ). 新的要求是我们要在同一应用程序实例( 多租户 )中支持多个客户端,每个客户端都具有单独的数据库。 We do not want controllers to be aware of existence of multiple databases. 我们不希望控制器知道多个数据库的存在。 We have catalog database from which given clients connection string may be obtained. 我们有目录数据库,可以从该数据库获取给定的客户端连接字符串。 Initial approach was to create common base for controllers that overrides Controller.Initialize since it is the first place we could get user identity and query catalog database for clients connection string and initialize database context. 最初的方法是为覆盖Controller的控制器创建通用基础。由于这是我们可以获取用户身份和查询目录数据库的客户端连接字符串并初始化数据库上下文的第一个位置,因此请进行初始化。 It worked nice until we found out a need to have users not connected to any specific database. 直到我们发现需要不将用户连接到任何特定数据库为止,它的工作效果都很好。 Then the idea was to throw exception in Initialize and catch it in exception filter to redirect user to page informing that this page features require being assign to database. 然后的想法是在Initialize中引发异常,并在异常过滤器中捕获该异常,以将用户重定向到页面,通知此页面功能需要分配给数据库。 Unfortunately Initialize is not action and exceptions thrown from it are not available to filters. 不幸的是,初始化不是动作,并且从它引发的异常对于过滤器不可用。

From your question, I understand that you are in the process of enabling tenant database model for the application that you are building. 根据您的问题,我了解到您正在为要构建的应用程序启用承租人数据库模型。 in that case, you should have a factory that does the following 在这种情况下,您应该有一家从事以下工作的工厂

  • Tenant resolution (either from the URL or based on any other input parameters) 租户解析(通过URL或基于任何其他输入参数)
  • Have a shard manager that looks up the connection string by a tenant identifier and then uses that to contact the right database for the tenant. 有一个分片管理器,它通过租户标识符查找连接字符串,然后使用该管理器联系租户的正确数据库。

In short, you should be having something like the one below 简而言之,您应该拥有类似下面的内容

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);
    }

The above is a generic interface that can be used to get the connectionstrings based on the established tenant context. 上面是一个通用接口,可用于基于已建立的租户上下文获取连接字符串。

The base database context would look something like the one below, 基本数据库上下文看起来类似于以下内容,

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)
        {

        }
}

A sample context would look like the one below, 示例上下文如下所示,

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; }
    }

The base service which can talk to the above context's would look like the one below 可以与上述上下文对话的基础服务看起来像下面的那种

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;
    }

With the sample entity service looking like the one below, 样本实体服务如下图所示,

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

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

The above code snippets illustrate a well tested and a good way to organize / architect your app for multi-tenancy. 上面的代码段说明了一种经过良好测试的好方法,可以组织/构建用于多租户的应用程序。

HTH HTH

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM