AndersonN.Omni.AutoApi.Abstractions 0.2.0

dotnet add package AndersonN.Omni.AutoApi.Abstractions --version 0.2.0
                    
NuGet\Install-Package AndersonN.Omni.AutoApi.Abstractions -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AndersonN.Omni.AutoApi.Abstractions" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AndersonN.Omni.AutoApi.Abstractions" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="AndersonN.Omni.AutoApi.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AndersonN.Omni.AutoApi.Abstractions --version 0.2.0
                    
#r "nuget: AndersonN.Omni.AutoApi.Abstractions, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AndersonN.Omni.AutoApi.Abstractions@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AndersonN.Omni.AutoApi.Abstractions&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=AndersonN.Omni.AutoApi.Abstractions&version=0.2.0
                    
Install as a Cake Tool

Omni.AutoApi

CI NuGet Downloads License: MIT

Mecanismo estilo ABP Auto API Controllers para ASP.NET Core (.NET 10): exponha Application Services como controllers HTTP automaticamente e consuma-os por clientes tipados — um proxy dinâmico em runtime ou um cliente real gerado em tempo de compilação. A mesma interface roda no servidor, no Blazor (Server/WebAssembly) e no MAUI.

dotnet add package AndersonN.Omni.AutoApi.AspNetCore
builder.Services.AddAutoApiServices();     // pronto: seus IRemoteService viram endpoints
app.MapControllers();

Destaques

  • Controllers automáticos a partir de IRemoteService — sem [ApiController]/[Route]/[HttpGet].
  • Clientes tipados em duas formas: proxy dinâmico (runtime, DispatchProxy) e cliente real gerado (compile-time, AOT-friendly) — ambos derivam a rota da mesma fonte, então não saem de sincronia.
  • Extensões de DI geradas: Add{Nome}Client(...) (via IHttpClientFactory quando disponível) e AddAllAutoApiClients(...) — incl. overload com configuração por serviço (multi-backend).
  • Validação automática: DataAnnotations inválidas → 400 ProblemDetails com erros por campo.
  • [Authorize] declarativo nos Application Services (metadata preservado no endpoint).
  • Upload via RemoteStreamContent (multipart) e streaming via IAsyncEnumerable<T> — nos dois clientes.
  • Base ApplicationService enriquecida: Logger, CurrentUser, GetRequiredService<T> sem construtor.
  • Erros padronizados em ProblemDetails (RFC 9457) + exceções de negócio.
  • Endpoint de definição /api/auto-api/definition (estilo ABP /api/abp/api-definition).
  • Rotas configuráveis (RouteOptions) e versionamento opt-in (Asp.Versioning).
  • Coberto por 80 testes (unit + integração). Licença MIT.

Pacotes

Pacote (NuGet) Descrição TFM
AndersonN.Omni.AutoApi.Abstractions IRemoteService, atributos, RemoteStreamContent e regras de rota/verbo net9.0; net10.0
AndersonN.Omni.AutoApi.AspNetCore Lado servidor: transforma IRemoteService em controllers MVC reais net9.0; net10.0
AndersonN.Omni.AutoApi.Client Lado cliente: proxy HTTP dinâmico (runtime, via DispatchProxy) + DI net9.0; net10.0
AndersonN.Omni.AutoApi.Client.SourceGenerator Gera o cliente HTTP real (XxxClient) + extensões de DI em compilação netstandard2.0 (analyzer)
dotnet add package AndersonN.Omni.AutoApi.AspNetCore   # servidor
dotnet add package AndersonN.Omni.AutoApi.Client       # cliente (proxy dinâmico)

Nome do pacote × namespace: os IDs no NuGet usam o prefixo AndersonN. (o prefixo Omni.* é reservado por outra conta no nuget.org), mas os namespaces C# continuam Omni.AutoApi.* — ou seja, você instala AndersonN.Omni.AutoApi.Client e escreve using Omni.AutoApi.Client;.

O pacote ...Client.SourceGenerator é um analisador e requer ...Abstractions no projeto consumidor (vem transitivamente ao usar ...AspNetCore ou ...Client).

Início rápido

Servidor — expondo um Application Service

public class TodoApplicationService : ApplicationService, ITodoAppService   // ApplicationService : IRemoteService
{
    public Task<List<TodoItem>> GetTodosAsync() => ...;          // GET    api/app-service/todo/get-todos
    public Task<TodoItem>       GetTodoAsync(int id) => ...;      // GET    api/app-service/todo/get-todo?id=
    public Task<TodoItem>       CreateTodoAsync(CreateTodoDto i); // POST   api/app-service/todo/create-todo  (body)
    public Task<TodoItem>       UpdateTodoAsync(int id, UpdateTodoDto i); // PUT  ...update-todo?id=  (body)
    public Task                 DeleteTodoAsync(int id);          // DELETE ...delete-todo?id=  -> 204
}
builder.Services.AddAutoApiServices();   // descobre IRemoteService, cria controllers, enriquece o OpenAPI
...
app.MapControllers();
app.MapAutoApiDefinition();               // (opcional) /api/auto-api/definition

Usando o serviço in-process (Blazor Server, jobs, testes) — além do endpoint HTTP:

builder.Services.AddAutoApiServer<ITodoAppService, TodoApplicationService>();
// ou, para todos os IRemoteService de um assembly:
builder.Services.AddAutoApiServers(typeof(TodoApplicationService).Assembly);

Prefira isso a um AddScoped manual: além de registrar, ele injeta o LazyServices, então Logger/CurrentUser/GetRequiredService funcionam igual no caminho in-process e no HTTP (com AddScoped puro, Logger viraria NullLogger silenciosamente e CurrentUser lançaria). Também garante que o assembly do serviço seja varrido, caso ele more numa class library.

Convenções: verbo pelo prefixo do método (Get/Create/Update/Delete/Patch), rota kebab-case, tipos simples → query, DTO complexo → body (exceto GET/DELETE). [Http*]/[Route]/[From*] explícitos são respeitados, e colisões de rota (sobrecargas) falham no startup com mensagem clara.

Cliente — mesma interface, registro por host

[AutoApiClient]                                   // habilita o cliente gerado
public interface ITodoAppService : IRemoteService { /* mesmos métodos */ }

(1) Proxy dinâmico (runtime, sem geração):

builder.Services.AddAutoApiClient<ITodoAppService>((_, c) => c.BaseAddress = new Uri(baseUrl));
// ou descobrindo todas as interfaces IRemoteService de um assembly:
builder.Services.AddAutoApiClients(typeof(ITodoAppService).Assembly, (_, c) => c.BaseAddress = new Uri(baseUrl));

(2) Cliente gerado (compile-time, AOT-friendly): o [AutoApiClient] faz o source generator emitir TodoAppServiceClient : ITodoAppService e extensões de DI (using Omni.AutoApi.Client.Generated;):

builder.Services.AddTodoAppServiceClient((_, http) => http.BaseAddress = new Uri(baseUrl)); // um cliente
builder.Services.AddAllAutoApiClients((_, http) => http.BaseAddress = new Uri(baseUrl));     // todos do assembly

// multi-backend: configuração POR SERVIÇO (o callback recebe o Type da interface):
builder.Services.AddAllAutoApiClients((_, http, svc) =>
    http.BaseAddress = new Uri(svc == typeof(IOrderAppService) ? ordersUrl : defaultUrl));

Quando Microsoft.Extensions.Http está presente, Add{Nome}Client usa AddHttpClient + typed client e retorna IHttpClientBuilder — encadeie resiliência e handlers (auth, correlação):

builder.Services.AddTodoAppServiceClient((_, http) => http.BaseAddress = new Uri(baseUrl))
    .AddHttpMessageHandler<AuthTokenHandler>()      // DelegatingHandler p/ Authorization: Bearer ...
    .AddStandardResilienceHandler();                 // Microsoft.Extensions.Http.Resilience

Recursos do servidor

Base ApplicationService enriquecida — helpers resolvidos sob demanda (injetados por um filtro do MVC, sem substituir o ativador de controllers), sem construtor:

public Task<List<TodoItem>> GetTodosAsync()
{
    Logger.LogInformation("user={User}", CurrentUser.Id);   // ILogger + ICurrentUser via LazyServices
    var repo = GetRequiredService<ITodoRepository>();
    ...
}

Pipeline de erro — exceções viram ProblemDetails (RFC 9457) com status mapeado; mensagens de negócio são preservadas, as de framework (4xx) são mascaradas:

throw new EntityNotFoundException("Todo não encontrado");  // -> 404 { "code": "EntityNotFound", ... }
throw new BusinessException("Saldo insuficiente");         // -> 409

Validação automática — DataAnnotations no DTO ([Required], [Range], ...) inválidas retornam 400 ProblemDetails com "code": "ValidationError" e erros por campo, sem código na action.

Autenticação e autorização — [Authorize]/policies na classe ou método do Application Service funcionam normalmente (o metadata é preservado no endpoint). No servidor, nada além do setup padrão de auth do ASP.NET Core:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(/* ... */);
builder.Services.AddAuthorization(o => o.AddPolicy("todo:admin", p => p.RequireRole("admin")));
// ...
app.UseAuthentication();
app.UseAuthorization();
public class TodoApplicationService : ApplicationService, ITodoAppService
{
    public Task<List<TodoItem>> GetTodosAsync() => ...;              // público

    [Authorize]                                                       // exige token válido -> 401
    public Task<TodoItem> CreateSecureTodoAsync(CreateTodoDto input) => ...;

    [Authorize(Policy = "todo:admin")]                                // exige a policy -> 403
    public Task DeleteAllTodosAsync() => ...;
}

Do lado cliente, use o AuthTokenHandler para anexar Authorization: Bearer … (o callback é chamado a cada requisição, então serve tanto para token fixo quanto para renovação):

builder.Services.AddSingleton(sp => new AuthTokenHandler(() => sp.GetRequiredService<ITokenStore>().Current));
builder.Services.AddTodoAppServiceClient((_, http) => http.BaseAddress = new Uri(baseUrl))
                .AddHttpMessageHandler<AuthTokenHandler>();

O sample Omni.AutoApi.Sample.Web traz o fluxo JWT completo (emissor de token de teste em /dev/token, serviço protegido e policy), coberto por testes de integração 200/401/403.

Upload e streaming — a mesma interface declara upload (sem depender de IFormFile) e streaming incremental, funcionando no servidor e nos dois clientes:

Task<string> CreateAttachmentAsync(RemoteStreamContent content);         // multipart/form-data
IAsyncEnumerable<TodoItem> GetTodoStreamAsync(CancellationToken ct = default); // JSON incremental

Rotas configuráveis (RouteOptions) — no servidor e no gerador (mantenha os dois em sincronia):

builder.Services.AddAutoApiServices(o => { o.Prefix = "api/services"; o.UseKebabCase = true; });

<PropertyGroup>
  <AutoApiRoutePrefix>api/services</AutoApiRoutePrefix>
  <AutoApiControllerPostfixes>AppService;Handler</AutoApiControllerPostfixes>
</PropertyGroup>

Versionamento opt-in — lê a versão por query (?api-version=2.0) ou header (X-Api-Version), sem mudar a rota:

builder.Services.AddAutoApiVersioning();
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class CatalogAppService : ApplicationService
{
    public Task<string> GetNameAsync() => ...;                       // nas duas versões

    [MapToApiVersion("1.0")] public Task<string> GetLegacyCodeAsync() => ...;   // só v1
    [MapToApiVersion("2.0")] public Task<Summary> GetSummaryAsync() => ...;     // só v2
}

Um documento OpenAPI por versão — DiscoverApiDocumentNames lê os [ApiVersion] e devolve ["v1", "v2"], então a biblioteca não precisa depender de um pacote de OpenAPI específico (o mesmo laço serve para AddOpenApi nativo, Swashbuckle ou NSwag):

foreach (var doc in ApiVersioningExtensions.DiscoverApiDocumentNames())
{
    builder.Services.AddOpenApi(doc);      // /openapi/v1.json e /openapi/v2.json
}

O endpoint de definição também fica ciente de versão: cada ação traz apiVersion, o documento lista apiVersions, e ?apiVersion=v2 filtra a definição para gerar um cliente só da v2.

Para ajustar o ApiExplorer (ex.: formato do nome do documento), use o options pattern: services.Configure<ApiExplorerOptions>(o => o.GroupNameFormat = "'v'VV");

Diagnósticos em tempo de compilação

O pacote AspNetCore traz um analisador que antecipa para a digitação erros que só apareceriam no startup — sem instalar nada a mais:

ID Severidade O que pega
AUTOAPI002 Warning Sobrecarga (ou par Foo/FooAsync) que gera rota duplicada — hoje derruba a aplicação ao iniciar
AUTOAPI003 Warning Mais de um parâmetro complexo em verbo com corpo — o MVC só aceita um [FromBody]
AUTOAPI004 Info Método não assíncrono: o endpoint funciona, mas os clientes tipados não o implementam
public class TodoAppService : ApplicationService
{
    public Task<Todo> GetTodoAsync(int id) => ...;
    public Task<Todo> GetTodoAsync(string slug) => ...;   // ⚠ AUTOAPI002: mesma rota GET /get-todo
}

Do lado cliente, o AUTOAPI001 (source generator) já sinaliza métodos que ele não consegue gerar. Para ajustar a severidade, use o .editorconfig: dotnet_diagnostic.AUTOAPI004.severity = none.

Exemplos (samples/)

Exemplo O que mostra
Omni.AutoApi.Sample.Web Host + consumidor básico; OpenAPI/Scalar; pipeline de erro.
BlazorMauiAuto Blazor Web App (InteractiveAuto: Server → WebAssembly) + MAUI compartilhando páginas, com a Omni.AutoApi como camada de dados (o mesmo ITodoAppService nos três hosts).
Distribution Distribuir o cliente gerado para outra solution via pacote de contratos (o cliente vem "assado" no DLL; o consumidor não precisa do gerador).

Estrutura do repositório

src/
  Omni.AutoApi.Abstractions/           # tipos compartilhados (fonte de verdade das rotas)
  Omni.AutoApi.AspNetCore/             # mecanismo server (convention, providers, DI, erro, versioning)
  Omni.AutoApi.Client/                 # proxy dinâmico de runtime + DI
  Omni.AutoApi.Client.SourceGenerator/ # gerador Roslyn (cliente + extensões de DI)
samples/
  Omni.AutoApi.Sample.Web/             # exemplo básico
  BlazorMauiAuto/                 # Blazor Auto + MAUI compartilhando UI/dados
  Distribution/                   # distribuição cross-solution via NuGet
tests/
  Omni.AutoApi.Tests/                  # unit (ApiRouteBuilder, TypeHelper, gerador)
  Omni.AutoApi.IntegrationTests/       # WebApplicationFactory (e2e)

Build / testes / empacotamento

dotnet build Omni.AutoApi.sln
dotnet test  Omni.AutoApi.sln
dotnet pack  Omni.AutoApi.sln -c Release -o artifacts   # gera os 4 .nupkg

Publicação

A publicação no nuget.org é automática via Trusted Publishing (OIDC — sem API key de longa duração). Basta criar a tag; o workflow release.yml usa a tag como versão dos pacotes:

git tag v0.2.0 && git push origin v0.2.0   # publica Omni.AutoApi.* 0.2.0

Limitações & notas

  • Sobrecargas de método não são suportadas (colisão de rota → falha no startup); use [HttpGet("rota")]/[Route].
  • Cliente gerado suporta Task/ValueTask(<T>) e IAsyncEnumerable<T>. Métodos genéricos, com mais de um parâmetro complexo (corpo) ou com Stream/IFormFile crus (use RemoteStreamContent) viram stub que lança NotSupportedException + diagnóstico AUTOAPI001.
  • Query string: contrato de serialização é ISO-8601 invariante para datas/horas (DateOnly → 2026-06-24) e nome para enums. DTOs complexos em GET/DELETE são achatados em 1 nível (propriedades complexas aninhadas são omitidas).
  • Tratamento de erro no cliente: chamam EnsureSuccessStatusCode() e propagam exceções (HttpRequestException/JsonException); aplique resiliência via IHttpClientBuilder retornado pelo registro gerado (.AddStandardResilienceHandler()).
  • Sincronia de contrato: rota/verbo derivam do mesmo ApiRouteBuilder, mas a assinatura não é validada contra o servidor — compartilhe a mesma interface (idealmente num assembly de contratos).
  • Dinâmico vs. gerado: o proxy dinâmico usa DispatchProxy + reflexão (zero setup); o gerado é código real (mais rápido, debugável, AOT-friendly). Mesma semântica de rota/binding.
  • Trimming/AOT: o caminho reflexivo do cliente (DTO complexo em GET/DELETE) já é anotado com [DynamicallyAccessedMembers]; ainda assim prefira parâmetros simples/body em apps trimmed.
  • BaseAddress: normalizada automaticamente (barra final); ausência gera erro claro na 1ª chamada.

Contribuindo

Veja CONTRIBUTING.md (build, testes, política de API pública) e SECURITY.md para reporte de vulnerabilidades.

Licença, changelog & roadmap

MIT. Histórico em CHANGELOG.md. Próximos passos e pendências priorizadas em ROADMAP.md.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on AndersonN.Omni.AutoApi.Abstractions:

Package Downloads
AndersonN.Omni.AutoApi.Client

Proxy de cliente HTTP dinâmico (runtime, via DispatchProxy) e registro DI para serviços remotos Auto API.

AndersonN.Omni.AutoApi.AspNetCore

Auto API Controllers para ASP.NET Core: expõe Application Services (IRemoteService) como controllers MVC reais, com rota/verbo/binding convencionais.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 305 8/8/2026
0.1.0 146 7/25/2026

Consulte o CHANGELOG.md do repositório.