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 é Guid e o valor resolvido for 00000000-0000-0000-0000-000000000000

  • O seu tipo é int e o valor resolvido for 0

  • O seu tipo é string e o valor resolvido for “” ou null

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 string para colunas VARCHAR, utilize entityTypeBuilder.ApplyVarcharColumnTypeForEntity().

  • Mapeamento de propriedades decimal para colunas DECIMAL(19,6), utilize entityTypeBuilder.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;
}