ASP.NET
Core - Aplicações Multi-Tenant - II
![]() |
Hoje veremos como criar uma aplicação Multi-Tenant em simples usando a ASP .NET Core 5. |
Continuando a primeira parte do artigo vamos agora criar uma aplicação Multi-Tenant bem simples usando ASP .NET Core 5.
Usando a resolução do Inquilino ou Tenant Resolution
Em uma aplicação Multi-Tenant precisamos ser capazes de identificar em qual locatário uma solicitação está sendo executada, mas antes de ficarmos muito animados, precisamos decidir quais dados exigimos para poder procurar um locatário. Na verdade, precisamos apenas de uma informação neste estágio, o identificador do locatário.
Assim na requisição HTTP, precisaremos decidir em qual contexto de locatário executar o request e isso afeta coisas como qual banco de dados acessar ou qual configuração usar.
Vamos usar aqui a resolução do inquilino que é a técnica pela qual você pode corresponder uma solicitação a um inquilino.
Existem várias maneiras de fazer isso; seu aplicativo pode armazenar dados de identificação do locatário de uma das seguintes maneiras:
Neste exemplo, vamos usar a última estratégia, Headers do Request, ou seja, os dados de identificação do locatário serão passados no cabeçalho do request e lidos a partir daí pelo aplicativo.
Assim vamos usar o identificador para corresponder a um locatário com base em nossa estratégia de resolução.
Criando a aplicação ASP .NET Core MVC
Abra o VS 2019 e clique em Create New Project selecionando o template ASP .NET Core Web App(Mode-View-Controller)
A seguir selecione as configurações conforme a figura abaixo:
Após criar o projeto inclua as referências as pacotes :
Criando o banco de dados , as tabelas e incluindo dados
Para manter as coisas simples, vamos usar um banco de dados com um design simples. Vamos criar um banco de dados no SQL Server chamado DemoTenant e neste banco de dados vamos criar as tabelas :
A seguir temos o script usado para criar essas tabelas:
USE DemoTenant GO CREATE TABLE dbo.Tenant( Id uniqueidentifier NOT NULL, ApiKey uniqueidentifier NOT NULL, TenantName nvarchar(200) NOT NULL, IsActive bit NOT NULL
CONSTRAINT PK_Tenant CREATE TABLE dbo.Customer( CONSTRAINT PK_Customer |
1- Estrutura da tabela Customer
2- Estrutura da tabela Tenant
A seguir temos o script usado para incluir dados em ambas as tabelas :
Use DemoTenant GO INSERT INTO dbo.Tenant(Id, ApiKey,TenantName,IsActive) INSERT INTO dbo.Tenant(Id, ApiKey,TenantName,IsActive) INSERT INTO dbo.Tenant(Id, ApiKey,TenantName, IsActive) INSERT dbo.Customer(Id,TenantId, CustomerName, IsActive) INSERT dbo.Customer(Id,TenantId, CustomerName, IsActive) INSERT dbo.Customer(Id,TenantId, CustomerName, IsActive) INSERT dbo.Customer(Id,TenantId, CustomerName, IsActive) INSERT dbo.Customer(Id, TenantId, CustomerName, IsActive)
|
Ao final teremos o seguinte resultado:
1- Dados da tabela Tenant
2- Dados da tabela Customer
Note que temos um relacionamento de um-para-muitos entre a tabela Tenant e a tabela Customer:
No arquivo appsettings.json vamos definir a seção ConnectionStrings contendo a string de conexão com o banco de dados DemoTenant:
{ "ConnectionStrings": { "TenantDbConnection": "Data Source=\\sqlexpress;Initial Catalog=DemoTenant; Integrated Security=True" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" } |
Criando as entidades e os repositórios
Na pasta Models do projeto vamos criar as entidades Customer e Tenant que representam o domínio da aplicação com o código a seguir:
1-Customer
public class Customer { public Guid Id { get; set; } public Guid TenantId { get; set; } public string CustomerName { get; set; } public bool IsActive { get; set; } } |
2-Tenant
public class Tenant { public Guid Id { get; set; } public Guid ApiIKey { get; set; } public string TenantName { get; set; } public bool IsActive { get; set; } } |
A seguir vamos criar a pasta Repositories no projeto e nesta pasta criar a classe CustomerRepository:
using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Threading.Tasks; using TenantWebApp.Models;
namespace TenantWebApp.Repositories
public async Task<List<Customer>> GetAllCustomers(string
tenantId)
using (var connection = new SqlConnection(_configuration
var reader = command.ExecuteReader();
if (!reader.IsClosed) await reader.CloseAsync();
if (connection.State != ConnectionState.Closed) await |
Neste código injetamos uma instância de IConfiguration e no método GetAllCustomers() estamos passando o Id do Tenant e acessando a tabela Customer para retornar todos os Customers para este Tenant.
A seguir vamos criar a a classe TenantRepository:
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using System; using System.Data; using System.Data.SqlClient; using System.Threading.Tasks;
namespace TenantWebApp.Repositories public TenantRepository(IConfiguration configuration, public async Task<string> GetTenantId(Guid apiKey) if (!reader.IsClosed) await reader.CloseAsync(); return tenantId; public async Task<string> GetTenantId() public async Task<string> GetAllTenantsAndCustomers() try using (var command = new SqlCommand("SELECT Tenant.Id, Tenant.ApiKey,
if (!reader.IsClosed) await reader.CloseAsync(); return FormatJsonData(result); |
Neste código temos os métodos :
Criando o Middleware e o método de extensão
Vamos criar a pasta Middlewares no projeto e nesta pasta vamos criar um middleware customizado definido na classe TenantSecurityMiddleware para ler a API key do cabeçalho do request e a seguir vamos usar este valor para recuperar o ID do locatário do banco de dados. Por último, o ID do inquilino recuperado é armazenado na sessão.
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using System; using System.Linq; using System.Threading.Tasks; using TenantWebApp.Repositories;
namespace TenantWebApp.Middlewares public TenantSecurityMiddleware(RequestDelegate next) public async Task Invoke(HttpContext context, IConfiguration configuration,
Guid apiKeyGuid; TenantRepository _tenentRepository = new TenantRepository(configuration, |
Para adicionar o middleware no pipeline de processamento do request vamos criar um método de extensão para IApplicationBuilder criando nesta mesma pasta a classe TenantSecurityMiddlewareExtension onde vamos definir o método UsetTenant:
using Microsoft.AspNetCore.Builder;
namespace TenantWebApp.Middlewares |
A seguir precisamos habilitar o pipeline para Session e para o middleware que criamos definindo o código no método Configure da classe Startup:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization();
app.UseSession(); app.UseEndpoints(endpoints => |
Concluindo as configurações vamos registrar no método ConfigureServices os serviços para os repositórios , Session e HttpContextAcessor:
public void
ConfigureServices(IServiceCollection
services) { services.AddControllersWithViews(); services.AddScoped<TenantRepository, TenantRepository>(); services.AddScoped<CustomerRepository, CustomerRepository>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(10); }); } |
Criando os controladores
Na pasta Controllers do projeto vamos criar primeiro o controlador CustomersController:
using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; using TenantWebApp.Models; using TenantWebApp.Repositories;
namespace TenantWebApp.Controllers TenantRepository tenantRepository) } [HttpGet] |
Neste código injetamos os repositórios no construtor e no método GetAll() vamos retornar todos os Customers pelo Id do Tenant:
Assim abrindo o Postman definimos a seguinte requisição:
Observe que obtemos o respectivo Customer
relacionado.
Agora vamos criar o controlador TenantController:
using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using TenantWebApp.Repositories;
namespace TenantWebApp.Controllers public TenantController(CustomerRepository customerRepository, TenantRepository tenantRepository) [HttpGet] |
Aqui vamos retornar todos os Tenants e Customers relacionados.
Para isso abra o Postman e defina a seguinte requisição:
Vemos a exibição dos Tenants e Customers:
[ { "Id": "A9121C5E-A37E-4C77-BD11-0CDDAAE46D4B", "ApiKey": "CAB37F08-10AA-473D-9F26-16D9D4A958E5", "TenantName": "Tenant C", "Customer": [ { "CustomerName": "Customer 5" }, { "CustomerName": "Customer 3" } ] }, { "Id": "47DF0EB1-AEF2-4208-8D03-AC88AE6C8330", "ApiKey": "A8AC37D4-715F-41E9-AE36-292CC6615E73", "TenantName": "Tenant B", "Customer": [ { "CustomerName": "Customer 2" } ] }, { "Id": "F3CFE18A-369B-4630-89B0-ED121D083C59", "ApiKey": "E71C77C8-E18A-4114-B659-006AF444C2DC", "TenantName": "Tenant A", "Customer": [ { "CustomerName": "Customer 1" }, { "CustomerName": "Customer 4" } ] } ] |
Temos assim um vislumbre de uma aplicação Multi-Tenant criada com a ASP .NET Core 5.
Dessa forma em um aplicativo Multi-Tenant à medida que o grau de multilocação aumenta, os custos do cliente diminuem. Em essência, um aplicativo multilocatário fornece mais valor ao longo do tempo.
Mesmo com todos os benefícios que eles oferecem, a flexibilidade e a segurança dos aplicativos multilocatários ainda é um desafio. Um aplicativo multilocatário pode não ser uma boa escolha se seu aplicativo precisar de muita personalização. Apesar dos desafios e desvantagens, a arquitetura Multi-Tenant é altamente desejável por causa de sua eficiência de custo e do valor comercial que fornece.
Pegue o código do
projeto aqui :
TenantWebApp.zip
"Amo ao SENHOR, porque ele ouviu a minha voz e a minha
súplica.
Porque inclinou a mim os seus ouvidos; portanto, o invocarei enquanto viver."
Salmos 116:1,2
Referências:
ASP .NET Core 2 - MiniCurso Básico
ASP .NET Core MVC - CRUD básico com ADO .NET
ASP .NET Core - Implementando a segurança com
ASP .NET Core - Iniciando com ASP .NET Core MVC e
ASP .NET Core MVC - Criando um site com MySql e EF
ASP .NET Core - Gerenciador de Despesas Pessoais com
Minicurso ASP .NET Core 2.0 - Apresentando MVC - YouTube
ASP .NET Core - Configurando o ambiente de .
ASP .NET Core e EF Core - Configurando o ambiente
ASP .NET Core - Como configurar o AutoMapper