Как обработать исключение, выброшенное из метода инициализации контроллера? - PullRequest
0 голосов
/ 01 ноября 2018

Вопрос:

Как обработать исключение, выброшенное из метода инициализации контроллера?

История вопроса:

У нас есть приложение .NET MVC, которое раньше работало с одной базой данных. У нас есть много контроллеров, которые создают контекст базы данных в конструкторе как член и затем используют его в действиях. Строка подключения была сохранена в Web.config. Новое требование заключается в том, что мы хотим поддерживать несколько клиентов, каждый из которых имеет отдельную базу данных в одном экземпляре приложения ( мультитенант ). Мы не хотим, чтобы контроллеры знали о существовании нескольких баз данных. У нас есть база данных каталога, из которой можно получить данную строку подключения клиентов. Первоначальный подход заключался в создании общей базы для контроллеров, которая переопределяет Controller.Initialize , поскольку это первое место, где мы можем получить идентификатор пользователя и базу данных каталога запросов для строки подключения клиентов и инициализировать контекст базы данных. Это работало хорошо, пока мы не обнаружили необходимость иметь пользователей, не подключенных к какой-либо конкретной базе данных. Тогда идея заключалась в том, чтобы добавить исключение в Initialize и перехватить его в фильтре исключений, чтобы перенаправить пользователя на страницу, сообщая, что функции этой страницы необходимо назначить базе данных. К сожалению, Initialize не является действием, и исключительные ситуации из него недоступны для фильтров.

1 Ответ

0 голосов
/ 22 декабря 2018

Из вашего вопроса я понимаю, что вы находитесь в процессе включения модели базы данных арендатора для создаваемого приложения. в этом случае у вас должна быть фабрика, которая выполняет следующее

  • Разрешение клиента (по 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;
        }

Приведенные выше фрагменты кода иллюстрируют хорошо протестированный и хороший способ организации / разработки приложения для мультитенантности.

НТН

...