Запрос DbContext из области видимости в концентраторе SignalR Core, передача ему строки подключения - PullRequest
0 голосов
/ 11 июня 2019

У меня есть ASP .Net Core 2.2 Web API с концентратором SignalR. Когда API получает сообщение от клиента, ему необходимо сохранить это сообщение в базе данных. Это делается следующим образом:

Концентратор SignalR:

public class ChatHub : Hub
{
    public async Task SendMessageToGroup(int clientId, int groupName, string message)
    {
        await SaveMessage(clientId, groupName, message);
        await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
    }

    private async Task<bool> SaveMessage(int clientId, string groupName, string message)
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();

            Message newMessage = new Message()
            {
                Message = message,
                GroupName = groupName,
                Timestamp = DateTime.Now
            };

            dbContext.Messages.Add(pwMessage);
            dbContext.SaveChanges();
       } 
       return true;
   }
}

Все будет хорошо, за исключением того факта, что это мультитенантное приложение. Обычно, когда клиент вызывает методы контроллера API с использованием HTTP-запросов, клиент отправляет через заголовок «TenantId» каждый запрос. Затем у меня есть промежуточное программное обеспечение, которое перехватывает этот запрос, извлекает TenantId из заголовка, вызывает службу для извлечения этого Tenant с использованием tenantId и сохраняет объект Tenant в HttpContext. Затем в методе OnConfiguring () объекта DbContext я использую этот объект арендатора (хранящийся в HttpContext), чтобы установить connectionString dbContext для любой базы данных, которую использует этот арендатор. Итак:

Middleware:

public class TenantIdentifier
{
    private readonly RequestDelegate _next;

    public TenantIdentifier(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
        Tenant tenant = await GetTenant(tenantId);
        httpContext.Items["Tenant"] = tenant;
        await _next.Invoke(httpContext);
    }
}

DbContext.cs:

public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}

public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
    _httpContextAccessor = httpContextAccessor;
}

protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
    string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
    optionsBuilder.UseMySql(connectionString);
}

Теперь, когда клиент вызывает концентратор SignalR, и я создаю новую область в концентраторе и запрашиваю DbContext, строка подключения имеет значение null. Это происходит потому, что, в отличие от HTTP-запроса, вызов концентратора SignalR не вызывает промежуточное программное обеспечение (которое отвечает за идентификацию арендатора)

Как я могу, при запросе DbContext из области, вручную передать ему строку подключения, вместо того, чтобы полагаться на нее, чтобы попытаться сгенерировать connectionString в событии OnConfiguring () (которое не будет работать)

Надеюсь, это имеет смысл: / Спасибо

1 Ответ

1 голос
/ 15 июня 2019

Если вы добавите IHttpContextAccessor в ваш конструктор класса Hub - сможете ли вы получить доступ к текущему контексту (и заголовкам) там?

public class ChatHub : Hub
{
    private IHttpContextAccessor currentContext;

    public ChatHub(IHttpContextAccessor currentContext)
    {
        this.currentContext = currentContext;
    }
}

Конечно, не забывая зарегистрировать HttpContextAccessor в DI:

public void ConfigureServices(IServiceCollection services)
{
     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
     services.AddHttpContextAccessor();
     services.AddTransient<IUserRepository, UserRepository>();
}
...