Crie agora seu próprio Framework modular para ser utilizado em suas aplicações ASP.NET CORE e Blazor.
Quando eu escrevo meus artigos, sempre crio uma nova aplicação a partir do template padrão do Visual Studio para você conseguir acompanhar o assunto com mais clareza.
E nesses artigos acabo duplicando muito código e deixando algumas coisas hardcoded, impossibilitando o reuso em outros projetos.
Eu quero transformar esses códigos fontes desses artigos em um único Framework que possa ser reutilizado em outros projetos ASP.NET CORE e Blazor.
Quando falo em Framework, eu digo que nesse contexto são uma ou mais DLLs C# .NET Standard com diversas funcionalidades genéricas que poderão ser utilizadas em outros projetos Web.
Algumas informações importantes para quem está chegando agora. Se você é um desenvolvedor .NET mas nunca viu nada de .NET Core, sugiro você ler antes o artigo .NET Core para DesenvolvedoreS .NET.
Com os conceitos desse artigo você poderá montar o seu próprio Framework C#.
Table of Contents
#Framework: Os artigos
Selecionei alguns artigos para serem utilizados como base deste, pois possuem diversas funcionalidades e Design Patterns como Repository e Dependency Injection.
Como estamos falando de um Framework, precisamos de funcionalidades genéricas, então cabe muito também usarmos algo de Cache, Extensions e Autenticação.
Fique livre para consultar os códigos fontes desses artigos:










#1 – A Arquitetura
Não é simplesmente juntar os códigos fontes desses artigos em uma ou mais DLL C# criando assim um Framework.
Precisamos de uma arquitetura de software que siga um padrão do começo ao fim.
Existem várias formas e padrões a serem utilizados como 3 camadas, DDD, TDD, XPTO, Ronaldo etc.
O mais importante não se trata de acertar ou errar, mas sim ser coerente.
O diagrama abaixo mostra como será a estrutura da solução:
Padrões de nomenclatura:
– Padrões de nomenclatura C#.
– Sufixo Provider para integração de uma library de terceiros.
– Sufixo Service para classe de regras de negócios.
– Sufixo Extension para Extention Methods.
– 1 DLL WEB que contem métodos e funcionalidades para API e MVC.
– 1 DLL CORE que contem métodos e funcionalidades genéricas e de Negócios.
– 1 DLL API que contem métodos e funconalidadaes específicas para API.
Para essa solução, criei duas aplicações “MyApp“, uma API e outra Blazor, ambas em ASP.NET CORE 3.0.
A aplicação FSL.MyApp.Blazor irá consumir a API de FSL.MyApp.Api.
As classes que implentam as interfaces desse framework começam com a palavra Blazor com intuito de facilitar, sabendo assim onde elas estão localizadas fisicamente na solution.
Seguindo a mesma coerência, as classes da API iniciam com a palavra API.
#2 – FSL.Framework.Core
Como definido na arquitetura desse framework, essa DLL Core conterá métodos e funcionalidades genéricos para todas as aplicações API e Blazor.
O Core foi devivido em módulos, são eles:
– Address: Módulo de endereço;
– ApiClient: Módulo para acesso a API;
– Authentication: Módulo de autenticação;
– Authorization: Módulo de autorização;
– Caching: Módulo de cache;
– Cryptography: Módulo de criptografias;
– Factory: Módulo para criação de instâncias de classes;
– Repository: Módulo de Repositórios de dados;
Seguindo os artigos previamente mencionados, além de todos esses módulos, algumas Models de apoio e Extension Methods também foram colocadas na DLL Core.
Destaque para a classe genérica (e básica) SqlRepository para conexão assíncrona com banco de dados SQL Server.
public class SqlRepository
{
private string _connectionStringId;
private readonly IDefaultConfiguration _configuration;
public SqlRepository(
IDefaultConfiguration configuration)
{
_configuration = configuration;
UseConnectionStringId(configuration?.ConnectionStringId ?? "Default");
}
protected async Task<T> WithConnectionAsync<T>(
Func<SqlConnection, Task<T>> getData)
{
using (var connection = CreateConnection())
{
await connection.OpenAsync();
var data = await getData(connection);
connection.Close();
return data;
};
}
protected SqlRepository UseConnectionStringId(
string connectionStringId)
{
_connectionStringId = connectionStringId;
return this;
}
private SqlConnection CreateConnection()
{
return new SqlConnection(_configuration.GetConnectionString(_connectionStringId));
}
}
#3 – FSL.Framework.Web
A DLL Web poderá ser utilizada tanto por API como aplicações Blazor em ASP.NET CORE 3.0 ou superior.
Nessa DLL constam as autenticações JSON Web Token e Cookies usadas respetivamente nos artigos JWT: Customizando o Identity no ASP.NET CORE 3.0 e Cookies: Identity no Blazor e ASP.NET CORE 3.0.
Também, contem versões melhoradas (e adaptadas) para acionamento de Cache e leitura de APIs externas, usadas respectivamente em Como Chamar Cache Provider em Uma Linha de Código no C# e Como Consumir API Restful no Xamarin Forms.
Destaque para classe de acionamento de APIs externas HttpClientApiClientProvider:
public sealed class HttpClientApiClientProvider : BaseApiClientProvider
{
private readonly HttpClient _httpClient;
public HttpClientApiClientProvider()
{
_httpClient = new HttpClient();
}
public override async Task<ApiClientResult<T>> GetAsync<T>(
string apiRoute)
{
_apiRoute = apiRoute;
try
{
AddReponseHeaders();
var response = await _httpClient.GetAsync($"{_apiUrlBase}{apiRoute}");
return await ConvertResponseToApiClientResultAsync<T>(response: response);
}
catch (Exception ex)
{
return await ConvertResponseToApiClientResultAsync<T>(ex: ex);
}
}
public override async Task<ApiClientResult<T>> PostAsync<T>(
string apiRoute,
object body)
{
_apiRoute = apiRoute;
try
{
AddReponseHeaders();
UseJsonContentType();
var response = await _httpClient.PostAsync(
$"{_apiUrlBase}{apiRoute}",
new StringContent(
body.ToJson(),
Encoding.UTF8,
_contentType));
return await ConvertResponseToApiClientResultAsync<T>(response: response);
}
catch (Exception ex)
{
return await ConvertResponseToApiClientResultAsync<T>(ex: ex);
}
}
private void AddReponseHeaders()
{
AddBearerTokenHeader();
foreach (var header in _headers)
{
_httpClient.DefaultRequestHeaders.Add(
header.Key,
header.Value);
}
}
}
Essa implementação genérica de IApiClientProvider, usa a classe HttpClient para comunicação entre APIs externas.
Futuramente poderíamos implementar esse provider com o RestSharp por exemplo.
O importante é usar sempre a interface IApiClientProvider e não a implementação (classe) em si.
Essa DLL tem apenas uma referência: a FSL.Framework.Core.
#4 – FSL.Framework.Api
Inicialmente a DLL de API possui apenas 2 Controllers: AddressController do módulo de endereços; e LoginController, para autorização e autenticação de usuário na API.
[Route("api/address")]
[ApiController]
public sealed class AddressController : ControllerBase
{
private readonly IAddressRepository _addressRepository;
public AddressController(
IAddressRepository addressRepository)
{
_addressRepository = addressRepository;
}
[HttpGet("{id}")]
public async Task<BaseResult<Address>> GetAsync(
string id)
{
var data = await _addressRepository.GetAddressAsync(id);
return data.ToResult();
}
[HttpGet("")]
public async Task<BaseResult<IEnumerable<Address>>> GetRangeAsync(
string start,
string end)
{
var data = await _addressRepository.GetAddressRangeAsync(
start,
end);
return data.ToResult();
}
}
[Route("api/login")]
[ApiController]
public sealed class LoginController : ControllerBase
{
private readonly IAuthenticationService _authenticationService;
private readonly Core.Authorization.Service.IAuthorizationService _authorizationService;
public LoginController(
IAuthenticationService authenticationService,
Core.Authorization.Service.IAuthorizationService authorizationService)
{
_authenticationService = authenticationService;
_authorizationService = authorizationService;
}
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> PostAsync(
[FromBody] LoginUser loginUser)
{
var authorization = await _authorizationService.AuthorizeAsync(loginUser);
if (!authorization.Success)
{
return Ok(authorization);
}
var authentication = await _authenticationService.AuthenticateAsync(authorization.Data);
if (!authentication.Success)
{
return Ok(authentication);
}
return Ok(authentication);
}
}
Repare que ambas as APIs são totalmente genéricas e podem ser reutilizadas em outros projetos API em ASP.NET CORE.
Essa DLL tem referências a FSL.Framework.Web e FSL.Framework.Core.
#5 – FSL.MyApp.Api
Essa DLL simula uso do framework dentro uma aplicação API em ASP.NET CORE.
Ela possui a implementação ApiAuthorizationService da interface IAuthorizationService responsável por autorizar um usuário a partir de login/email e senha.
public sealed class ApiAuthorizationService : IAuthorizationService
{
public async Task<BaseResult<IUser>> AuthorizeAsync(
LoginUser loginUser)
{
var loginOrEmail = loginUser?.LoginOrEmail ?? "";
var password = loginUser?.Password ?? "";
var result = new BaseResult<IUser>();
if (loginOrEmail == "fsl" && password == "1234")
{
result.Success = true;
result.Message = "User authorized!";
result.Data = new MyLoggedUser
{
Id = Guid.NewGuid().ToString(),
Name = "Name test",
Credentials = "01|02|09",
IsAdmin = false
};
}
else
{
result.Success = false;
result.Message = "Not authorized!";
}
return await Task.FromResult(result);
}
}
Obviamente, como pode ver, ela é uma classe Fake, sem uma implementação real, justamente porque isso dependerá de aplicação para aplicação.
Ao consumir esse framework, e se você quiser usar autorização/autenticação, será necessário implementar a interface IAuthorizationService.
Além dos Controllers de usuários e repositórios de Endereço, é interessante destacar que essa DLL foi preparada para ser publicada no IIS.
Por fim, eu criei alguns Extension Methods para facilitar a configuração desse framework no arquivo Startup.cs, vide:
public void ConfigureServices(
IServiceCollection services)
{
services
.AddApiFslFramework(Configuration) // here
.Config(opt =>
{
opt.AddDefaultConfiguration();
opt.AddJwtAuthentication();
opt.AddAuthorizationService<ApiAuthorizationService>();
opt.AddAddressRepository<ApiAddressSqlRepository>();
});
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseApiFslFramework(); // here
}
No caso, a MyApp.Api foi configurada para API (AddApiFslFramework), configurando (Config) leitura padrão de arquivo AppSettings.JSON (AddDefaultConfiguration) e autenticação por JWT (AddJwtAuthentication).
O código fonte do Extension AddApiFslFramework usado acima ficou assim:
public static class FslFrameworkExtension
{
public static FslFrameworkOptions AddApiFslFramework(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddCors();
services.AddControllers();
services.AddFslFramework(configuration);
var options = new FslFrameworkOptions(
services,
configuration);
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.IgnoreNullValues = true;
});
return options;
}
public static IApplicationBuilder UseApiFslFramework(
this IApplicationBuilder app)
{
app.UseCors(option => option.AllowAnyOrigin());
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
return app;
}
private static IServiceCollection AddFslFramework(
this IServiceCollection services,
IConfiguration configuration)
{
services.UseCaching();
services.AddConfiguration<CryptographyConfiguration>(configuration);
services.UseCryptography();
services.AddApiClient();
return services;
}
}
O arquivo AppSettings.JSON da aplicação API ficou assim:
{
"ConnectionStrings": {
"Default": "Data Source=.\\SQLEXPRESS2008R2;Initial Catalog=consulta_cep;User ID=sa;Password=1234567890;Persist Security Info=False;Connect Timeout=200000"
},
"TokenConfiguration": {
"ValidAudience": "FSL",
"ValidIssuer": "FSL",
"ValidateIssuerSigningKey": true,
"ValidateLifetime": true,
"ExpirationInSeconds": 60000
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Não vou discutir cada uma das configurações porque elas já foram explicadas no artigos que deram base a esse.
#6 – FSL.MyApp.Blazor
O arquivo de configuração Startup.cs na aplicação Blazor ficou assim:
public void ConfigureServices(
IServiceCollection services)
{
services
.AddBlazorFslFramework(Configuration) // here
.Config(opt =>
{
opt.AddDefaultConfiguration();
opt.AddConfiguration<MyBlazorConfiguration>();
opt.AddCookiesAuthentication();
opt.AddAuthorizationService<BlazorAuthorizationService>();
opt.AddAddressRepository<BlazorAddressRepository>();
opt.AddFactoryService<BlazorFactoryService>();
opt.AddApiClientService<BlazorApiClientService>();
});
services.AddSingleton<WeatherForecastService>();
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseBlazorFslFramework(); // here
}
Como pode ver, é a mesma lógica usada na API, só que nesse caso a configuração de AddBlazorFslFramework usa autenticação Cookies, customiza a leitura do arquivo AppSettings.JSON através de MyBlazorConfiguration e customiza alguns módulos como ApiClient, Factory e Address.
O código fonte do Extension AddBlazorFslFramework usado acima ficou assim:
public static class FslFrameworkExtension
{
public static FslFrameworkOptions AddBlazorFslFramework(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddFslFramework(configuration);
var options = new FslFrameworkOptions(
services,
configuration);
return options;
}
public static IApplicationBuilder UseBlazorFslFramework(
this IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
return app;
}
}
Destaque para a implementação BlazorApiClientService da interface IApiClientService do módulo de ApiClient.
public sealed class BlazorApiClientService : IApiClientService
{
private readonly MyBlazorConfiguration _configuration;
private readonly IFactoryService _factoryService;
private readonly ILoggedUserService _loggedUserService;
public BlazorApiClientService(
MyBlazorConfiguration configuration,
IFactoryService factoryService,
ILoggedUserService loggedUserService)
{
_configuration = configuration;
_factoryService = factoryService;
_loggedUserService = loggedUserService;
}
public async Task<IApiClientProvider> CreateInstanceAsync()
{
var instance = _factoryService
.InstanceOf<IApiClientProvider>()
.UseJsonContentType()
.UseBaseUrl(_configuration.ApiUrl);
var user = await _loggedUserService.GetLoggedUserAsync<MyLoggedUser>();
instance.UseAuthenticationBearer(user?.Data?.AccessToken);
return instance;
}
}
Essa classe cria uma instância para IApiClientProvider e pré-configura alguns parâmetros como URL BASE de API, AccessToken caso o usuário esteja logado e JSON ContentType.
O arquivo AppSettings.JSON da aplicação Blazor ficou assim:
{
"CookiesConfiguration": {
"ExpirationInSeconds": 60000,
"CookieName": "FSLMyAppBlazor"
},
"CryptographyConfiguration": {
"CryptographicKey": "FSLSampleKey"
},
"MyBlazorConfiguration": {
"ApiUrl": "http://localhost/fsl-myapp-api/api/"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Obs.: a propriedade ApiUrl de MyBlazorConfiguration guarda o endereço publicado da API FSL.MyApp.Api.
Outro destaque usado nessa aplicação Blazor é o Repository do módulo Address.
Ao invés de ir no banco de dados SQL trazer os endereços, ele aciona a API de FSL.MyApp.Api justamente através da interface IApiClientService.
public sealed class BlazorAddressRepository : IAddressRepository
{
private readonly IApiClientService _apiClientService;
public BlazorAddressRepository(
IApiClientService apiClientService)
{
_apiClientService = apiClientService;
}
public async Task<Address> GetAddressAsync(
string zipCode)
{
var apiClient = await _apiClientService.CreateInstanceAsync();
var result = await apiClient.GetAsync<Address>($"address/{zipCode}");
return result.Data;
}
public async Task<IEnumerable<Address>> GetAddressRangeAsync(
string start,
string end)
{
var apiClient = await _apiClientService.CreateInstanceAsync();
var result = await apiClient.GetAsync<List<Address>>($"address?start={start}&end={end}");
return result.Data;
}
}
Ao usar a interface IAddressRepository, não se sabe de onde é a origem dos dados, se é de API externa, bancos de dados SQL/Oracle/MySQL/MongoDB ou de outras fontes.
Por fim, vale comentar a implementação BlazorAuthorizationService de IAuthorizationService responsável pela autorização do usuário na aplicação Blazor.
public sealed class BlazorAuthorizationService : IAuthorizationService
{
private readonly IApiClientService _apiClientService;
public BlazorAuthorizationService(
IApiClientService apiClientService)
{
_apiClientService = apiClientService;
}
public async Task<BaseResult<IUser>> AuthorizeAsync(
LoginUser loginUser)
{
var result = new BaseResult<IUser>();
try
{
var instance = await _apiClientService.CreateInstanceAsync();
var login = await instance.PostAsync<AuthenticationResult>(
"login",
loginUser);
if (login.Success
&& login.Data.Authenticated)
{
var user = await instance
.UseAuthenticationBearer(login.Data.AccessToken)
.GetAsync<MyLoggedUser>("user");
user.Data.AccessToken = login.Data.AccessToken;
result.Data = user.Data;
result.Success = result.Data.IsNotNull();
}
}
catch (Exception ex)
{
result.Message = ex.ToString();
result.Success = false;
}
return result;
}
}
A autorização é feita através de um POST no endpoint login da API FSL.MyApp.Api.
Ao ser autenticado com sucesso na API, é acionado outro endpoint para retornar mais dados do usuário logado.
Para saber mais sobre o LOGIN completo da aplicação Blazor leia meu artigo Cookies: Identity no Blazor e ASP.NET CORE 3.0.
#7 – FslFrameworkOptions
Uma coisa que ficou pendente mencionar foi a classe FslFrameworkOptions, responsável pela configuração no arquivo Startup.cs.
Então, recapitulando seu uso:
public void ConfigureServices(
IServiceCollection services)
{
services
.AddApiFslFramework(Configuration)
.Config(opt => // here
{
opt.AddDefaultConfiguration();
opt.AddJwtAuthentication();
opt.AddAuthorizationService<ApiAuthorizationService>();
opt.AddAddressRepository<ApiAddressSqlRepository>();
});
}
O código fonte de FslFrameworkOptions ficou assim:
´
public sealed class FslFrameworkOptions
{
private readonly IServiceCollection _services;
private readonly IConfiguration _configuration;
public FslFrameworkOptions(
IServiceCollection services,
IConfiguration configuration)
{
_services = services;
_configuration = configuration;
}
public FslFrameworkOptions Config(
Action<FslFrameworkOptions> opt)
{
opt(this);
return this;
}
public FslFrameworkOptions AddDefaultConfiguration()
{
_services.AddConfiguration<DefaultConfiguration>(_configuration);
return this;
}
public FslFrameworkOptions AddConfiguration<T>()
where T : class
{
_services.AddConfiguration<T>(_configuration);
return this;
}
public FslFrameworkOptions AddJwtAuthentication()
{
_services.AddJwtAuthentication(_configuration);
return this;
}
public FslFrameworkOptions AddCookiesAuthentication()
{
_services.AddCookiesAuthentication(_configuration);
return this;
}
public FslFrameworkOptions AddApiClientService<T>()
where T : class, IApiClientService
{
_services.AddSingleton<IApiClientService, T>();
return this;
}
public FslFrameworkOptions AddApiClientProvider<T>()
where T : class, IApiClientProvider
{
_services.AddTransient<IApiClientProvider, T>();
return this;
}
public FslFrameworkOptions AddApiClient()
{
_services.AddApiClient();
return this;
}
public FslFrameworkOptions AddFactoryService<T>()
where T : class,IFactoryService
{
_services.AddSingleton<IFactoryService, T>();
return this;
}
public FslFrameworkOptions AddAuthorizationService<T>()
where T : class, IAuthorizationService
{
_services.AddSingleton<IAuthorizationService, T>();
return this;
}
public FslFrameworkOptions AddAddressRepository<T>()
where T : class, IAddressRepository
{
_services.AddSingleton<IAddressRepository, T>();
return this;
}
}
Interessante ressaltar aqueles métodos genéricos onde é necessário passar um tipo T, onde (keyword where) T precisa ser uma classe e de uma interface específica, como por exemplo o método AddAddressRepository da interface IAddressRepository.
#8 – Considerações finais
Baixe o código fonte no meu github e deixe o projeto MyApp.Api (ou MyApp.Blazor) como default.
Restaure o NuGet e compile pra ver se tudo está OK.
Para testar a aplicação MyApp.Blazor, você precisará publicar (localmente) o projeto MyApp.Api.
Espero que tenha curtido esse artigo e o trabalho que venho desempenhando, e para você criar um framework faz sentido no seu dia a dia?
Obrigado e até a próxima 🙂
Artigos sobre Blazor e ASP.NET CORE:
Cookies: Identity no Blazor e ASP.NET CORE 3.0
Blazor: Muito mais que um WebForms de Luxo
Benchmark: ASP.NET 4.8 vs ASP.NET CORE 3.0
AppSettings: 6 Formas de Ler o Config no ASP.NET CORE 3.0
JWT: Customizando o Identity no ASP.NET CORE 3.0
Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS
IIS: Como Hospedar Aplicação .NET Core em 10 Passos
.NET Core para Desenvolvedores .NET
Blazor: O Começo do Fim do Javascript?
| Faça download completo do código fonte no github. |



