Guias
Implementando um AmbientData contributor
Os contributors servem para prover um AmbientData no fluxo atual. Antes de implementar, você terá que visualizar na lista abaixo qual a interface correta para implementação
AmbientData |
Interface a ser implementada |
|---|---|
TenantId |
ITenancyResolveContributor |
UserId |
IUserResolveContributor |
CompanyId |
ICompanyResolveContributor |
EnvironmentId |
IEnvironmentResolveContributor |
LegacyCompanyId |
ILegacyCompanyResolveContributor |
NotificationUpdateId |
INotificationUpdateContributor |
ApiVersion |
IApiVersionResolveContributor |
Declare a classe herdando do contributor apropriado
public class TenantIdFromHeadersResolveContributor: ITenancyResolveContributor
{
public ValueTask<AmbientDataResolveResult> TryResolveTenant()
{
}
}
Como o retorno das interfaces sempre é AmbientDataResolveResult, note que terá que especificar o tipo do AmbientData pelo generic type no método AmbientDataResolveResult<>.Handle. Esse método automaticamente verifica se o valor informado no parâmetro value é vazio/nulo, potencialmente fazendo com que o seu contributor seja desconsiderado. O valor é considerado vazio/nulo quando:
O seu tipo é
Guide o valor resolvido for00000000-0000-0000-0000-000000000000O seu tipo é
inte o valor resolvido for0O seu tipo é
stringe o valor resolvido for “” ounull
Warning
Quando o tipo for string utilize o método HandleString
Abaixo um exemplo de contributor que resolve o TenantId dos headers da requisição
public class TenantIdFromHeadersResolveContributor: ITenancyResolveContributor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RequestHeadersTenancyResolveContributor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ValueTask<AmbientDataResolveResult> TryResolveTenant()
{
if (_httpContextAccessor.HttpContext is not null)
{
if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AmbientDataConsts.TenantId, out var tenantIdStr))
{
if (Guid.TryParse(tenantIdStr, out var tenantId))
{
return ValueTask.FromResult(AmbientDataResolveResult<Guid>.Handle(tenantId));
}
}
}
return ValueTask.FromResult(AmbientDataResolveResult.NotHandled);
}
}
Desabilitando a obrigatoriedade de um AmbientData
Para desablitar a obrigatorieade de um AmbientData, anote o método com um dos seguintes atributos
AmbientData |
Atributo |
|---|---|
Todos* |
AmbientDataNotRequired |
TenantId |
TenantNotRequired |
UserId |
UserNotRequired |
CompanyId |
CompanyNotRequired |
EnvironmentId |
EnvironmentNotRequired |
LegacyCompanyId |
LegacyCompanyNotRequired |
Obs: Ao anotar a sua classe ou método com AmbientDataNotRequired, o SDK não obrigará nenhum AmbientData
Note
ApiVersion e NotificationUpdateId não são obrigatórios e portanto não geram erro.
Esses atributos podem ser utilizados em handlers de ServiceBus (IHandleMessages<>), controllers e background jobs (IBackgroundJob<>). Eles tem efeito quando anotados em método ou a nível de classe, nesse segundo caso valerá para todos os métodos da classe.
Para desabilitar a obrigatorieade globalmente, a configuração deverá ser feita a nível de Startup utilizando o parâmetro options. Ajuste os requerimentos conforme a sua necessidade.
public void ConfigureServices(IServiceCollection services)
{
services.AddMultiTenancy(MultiTenancyOptions.Default().CompanyNotRequired().EnvironmentNotRequired().TenantNotRequired())
.AddUserIdentity(UserIdentityOptions.Default().UserNotRequired())
}
Se a lógica para desabilitar a obrigatoriedade for complexa, será necessário implementar o método IsRequired.
A interface a ser implementada varia conforme o AmbientData
AmbientData |
Interface a ser implementada |
|---|---|
TenantId |
ITenantAmbientDataRequiredContributor |
UserId |
IUserAmbientDataRequiredContributor |
CompanyId |
ICompanyAmbientDataRequiredContributor |
EnvironmentId |
IEnvironmentAmbientDataRequiredContributor |
LegacyCompanyId |
ILegacyCompanyAmbientDataRequiredContributor |
Sempre inicialize o retorno do método IsRequired com true, pois desse jeito não correrá risco de desobrigar o AmbientData em um fluxo não esperado.
Os casos em que deverá retornar true por padrão são:
a operação atual não tem relação ao que está sendo verificado (o contributor serve para requisições HTTP porém a ação atual é de um handler)
a operação atual tem relação ao que está sendo verificado, porém a condição para desativação não é verdadeira
public class ServiceBusCompanyAmbientDataRequiredContributor: ICompanyAmbientDataRequiredContributor
{
public Task<bool> IsRequired()
{
var isRequired = true;
if (MessageContextExtensions.IsHandlingMessage())
{
isRequired = VerificarObrigatoriedade()
}
return Task.FromResult(isRequired);
}
}
Injetando serviços customizados no DbContext
Realize a injeção das dependências no seu dbcontext:
private TenantProperties _tenantProperties;
public PostgreSqlDbProviderTestServiceDbContext(DbContextOptions options, ISchemaNameProvider schemaNameProvider, ILoggerFactory loggerFactory, IBaseDbContextConfigurationService configurationService, TenantProperties tenantProperties)
: base(options, schemaNameProvider, loggerFactory, configurationService)
{
_tenantProperties = tenantProperties;
}
Na classe que DbContextDesignTime que implementa a interface PostgreSqlBaseDesignTimeDbContextFactory ou SqlServerBaseDesignTimeDbContextFactory liste os tipos das dependências na propriedade AllowedParameters no contrutor da classe:
public class DbProviderTestServiceDbContextDesignTime : PostgreSqlBaseDesignTimeDbContextFactory<PostgreSqlDbProviderTestServiceDbContext>
{
public DbProviderTestServiceDbContextDesignTime()
{
AllowedParameters.Add(typeof(ITenantProperties));
}
}
Mapeando as entidades para utilizarem VARCHAR ou DECIMAL
Há disponível dois métodos de extensão para mapeamento da entidade no banco de dados SQL Server.
Mapeamento de propriedades
stringpara colunasVARCHAR, utilizeentityTypeBuilder.ApplyVarcharColumnTypeForEntity().Mapeamento de propriedades
decimalpara colunasDECIMAL(19,6), utilizeentityTypeBuilder.ApplyDecimalMappingForEntity().
Caso a extensão ApplyVarcharColumnTypeForEntity não esteja disponível, instale o pacote Viasoft.Core.EntityFrameworkCore.SQLServer.Legacy.
Warning
A extensão que mapeia para VARCHAR somente deve se utilizada nas entidade que acessarem o banco de dados do ERP desktop.
No método OnModelCreating do seu DbContext, busque a entidade a ser configurada utilizando o método Entity<> do ModelBuilder.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var estoque = modelBuilder.Entity<Estoque>();
}
Utilize os métodos de extensão ApplyVarcharColumnTypeForEntity ou ApplyDecimalMappingForEntity para configurar.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var estoque = modelBuilder.Entity<Estoque>();
stoque.ToTable("ESTOQUE");
estoque
.Property(entity => entity.Codigo)
.HasColumnName("CODIGO")
.HasMaxLength(50);
estoque
.Property(entity => entity.Descricao)
.HasColumnName("DESCRI")
.HasMaxLength(200);
estoque
.Property(entity => entity.SaldoReal)
.HasColumnName("SALDO");
estoque.ApplyDecimalMappingForEntity();
estoque.ApplyVarcharColumnTypeForEntity();
}
Configurando as consultas do AdvancedFilter para case sensitive/insensitive
Para configurar as consultas do AdvancedFilter, utilize a flag BuildExpressionOptions.Default.CaseInsensitiveStringComparision no Startup.
Case sensitive
public void ConfigureServices(IServiceCollection services)
{
BuildExpressionOptions.Default.CaseInsensitiveStringComparision = false;
}
Case insensitive
public void ConfigureServices(IServiceCollection services)
{
BuildExpressionOptions.Default.CaseInsensitiveStringComparision = true;
}
Warning
Os serviços que acessam SQL Server devem utilizar a flag com o valor false pois a configuração de sensitivity está a nível de banco de dados.
Utilizando o System.Text.JSON nas chamadas HTTP
Para utilizar o serializdor System.Text.JSON.JsonSerializer utilize o método UseNewSerializer informando os dois parâmetros como true.
public Task<Cliente> RequisitarCliente(Guid id)
{
var call = _apiClientCallBuilder
.WithEndpoint($"/clientes/{id}")
.WithServiceName(Endpoints.ServiceName)
.WithHttpMethod(HttpMethod.Get)
.UseNewSerializer(true, true)
.Build();
var result = await call.ResponseCallAsync<Cliente>();
return result;
}