https://github.com/dzmprt/mitmediator.autoapi
Extension for MitMediator that auto-registers API endpoints and generates HTTP clients from request types
https://github.com/dzmprt/mitmediator.autoapi
autoapi http-client mediator mitmediator mitmediator-autoapi
Last synced: about 2 months ago
JSON representation
Extension for MitMediator that auto-registers API endpoints and generates HTTP clients from request types
- Host: GitHub
- URL: https://github.com/dzmprt/mitmediator.autoapi
- Owner: dzmprt
- License: mit
- Created: 2025-07-05T13:39:49.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-06-03T20:24:18.000Z (about 2 months ago)
- Last Synced: 2026-06-03T21:11:46.937Z (about 2 months ago)
- Topics: autoapi, http-client, mediator, mitmediator, mitmediator-autoapi
- Language: C#
- Homepage:
- Size: 1.65 MB
- Stars: 12
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/dzmprt/MitMediator/actions/workflows/dotnet.yml)



# MitMediator.AutoApi
## An extension for [MitMediator](https://github.com/dzmprt/MitMediator) that automatically registers API endpoints and generates strongly-typed HTTP clients from mediator request types, with customizable behavior driven by attributes
- [π Installation](#-Installation)
- [How It Works](#how-it-works)
- [Examples](#examples)
- [Change default mapping](#change-default-mapping)
- [Upload and download files](#upload-and-download-files)
- [X-Total-Count Header](#x-total-count-header)
- [Location header](#location-header)
- [Auto client HttpMediator](#auto-client-httpmediator)
- [Samples](./samples)
- [License](LICENSE)
## π Installation
### 1. Add package
```bash
# for ASP.NET API projects
dotnet add package MitMediator.AutoApi -v 10.0.0
# for application layer
dotnet add package MitMediator.AutoApi.Abstractions -v 10.0.0
# for client application (MAUI, Blazor, UWP, etc.)
dotnet add package MitMediator.AutoApi.HttpMediator -v 10.0.0
```
### 2. Use extension for IEndpointRouteBuilder
```csharp
var builder = WebApplication.CreateBuilder(args);
// Register handlers and IMediator.
builder.Services.AddMitMediator();
var app = builder.Build();
// Automatically maps endpoints based on IRequest
app.UseAutoApi();
app.Run();
```
> To use base path "**api**" and select assemblies to scan: `app.UseAutoApi("api", new []{typeof(GetQuery).Assembly});`
### 3. Done! All public requests have endpoints
## How It Works
1. Scans all loaded assemblies for `IRequest` types
2. Dynamically generates and registers endpoints for each match
3. Maps routes using `MapPost`, `MapGet`, `MapPut`, and `MapDelete`, internally calling `IMediator.SendAsync`
### Transformation Logic
HTTP Method is inferred from the leading verb in the type name:
| Request name start with | HTTP Method |
|-------------------------|---------------------|
| `get` | GET |
| `load` | GET |
| `download` | GET |
| `fetch` | GET |
| `update` | PUT |
| `change` | PUT |
| `edit` | PUT |
| `modify` | PUT |
| `put` | PUT |
| `post` | POST |
| `import` | POST |
| `upload` | POST |
| `add` | POST (201 response) |
| `create` | POST (201 response) |
| `delete` | DELETE |
| `remove` | DELETE |
| `drop` | DELETE |
Resource Name (main tag) is derived from the first noun after the verb, and pluralized:
- Book -> /books
- Author -> /authors
Route suffix includes any remaining parts of the type name, converted to lowercase and kebab-case:
- Count -> /count
- WithAuthor -> /with-author
> Custom suffix will be added as is
Suffixes `Command`, `Query`, and `Request` are automatically removed from the end
Version prefix (v1, v2, etc.) is prepended to the route as part of the base path
> Default version is `v1`
## Examples
| Request name | Endpoint |
|-----------------------------|--------------------------------|
| GetBookCountQuery | `GET /v1/books/count` |
| CreateBookCommand | `POST /v1/books` |
| RemoveBookWithAuthorCommand | `DELETE /v1/books/with-author` |
| UpdateAuthorBioRequest | `PUT /v1/authors/bio` |
### `GET` endpoint with query params
```csharp
// Api URL: GET /v1/books?limit=1&offset=1&freeText=clara
public struct GetBooksQuery : IRequest
{
public int? Limit { get; init; }
public int? Offset { get; init; }
public string? FreeText { get; init; }
}
```
### `POST` endpoint with 201 response
```csharp
// Api URL: POST /v1/books
public class CreateBookCommand : IRequest
{
public string Title { get; init; }
public int AuthorId { get; init; }
public string GenreName { get; init; }
}
```
If you need to use a route parameter, inherit from one of the classes `KeyRequest` (recomended)
or implement one of the interfaces `IKeyRequest`, `IKeyRequest`, etc.
(up to 7 keys).
### `GET` endpoint with suffix and key in url
```csharp
// URL: GET /v1/books/123/cover
public class GetBookCoverQuery : KeyRequest, IRequest;
```
Use interface `IKeyRequest` for structs or when you can't inherit KeyRequest:
> ```csharp
> // URL: GET /v1/books/123/cover
> public struct GetBookCoverQuery : IKeyRequest, IRequest
> {
> internal int BookId { get; private set; }
>
> public void SetKey(int key)
> {
> BookId = key;
> }
>
> public int GetKey() => BookId;
> }
> ```
## Change default mapping
Use attributes for the request type to change default mapping
Supported attributes:
| Attribute | Template | Result for `GetBookRequest` |
|--------------------------------|------------------------------------------------|-----------------------------------------------------------|
| `TagAttribute` | `[Tag("CustomTag")]` | `GET v1/custom-tag` |
| `VersionAttribute` | `[Version("v2")]` | `GET v2/books` |
| `SuffixAttribute` | `[Suffix("cover")]` | `GET v1/books/cover` |
| `PatternAttribute` | `[Pattern("api/v3/books/{key1}/page/{key2}")]` | `GET api/v3/books/{key1}/page/{key2}` |
| `MethodAttribute` | `[Method(MethodType.Post)]` | `POST v1/books` |
| `DescriptionAttribute` | `[Description("My custom description")]` | See description (in xml doc or swagger) |
| `DisableAntiforgeryAttribute` | `[DisableAntiforgery]` | Antiforgery protection has been disabled for the endpoint |
| `ResponseContentTypeAttribute` | `[ResponseContentType("image/png")]` | Result will have selected content type |
| `IgnoreRequestAttribute` | `[IgnoreRequest]` | Request will be ignored π« |
When `[PatternAttribute]` is applied, it overrides the entire route. The base route, version attribute, and tag attribute will be ignored
### Examples
### `GET` endpoint with custom tag and custom suffix
```csharp
// URL: GET /v1/my-books/favorite?limit=1&offset=1&freeText=clara
[Tag("MyBooks")]
[Suffix("favorite")]
public struct GetBooksQuery : IRequest
{
public int? Limit { get; init; }
public int? Offset { get; init; }
public string? FreeText { get; init; }
}
```
### `GET` endpoint with version, custom tag, URL pattern and specified HTTP method
```csharp
// Custom URL pattern overrides base route
// URL: DELETE /with-keys/{key1}/field/{key2}
[Tag("books")]
[Pattern("with-keys/{key1}/field/{key2}")]
[Version("v3")]
[Method(MethodType.Delete)]
public class DoSomeWithBookAndDeleteCommand : KeyRequest, IRequest;
```
### `GET` endpoint png file ("image/png")
```csharp
// Api URL: GET /v1/books/123/Ρover
[ResponseContentType("image/png")]
public class GetBookCoverQuery: KeyRequest, IRequest;
```
## Upload and download files
For requests implementing `IRequest`/`IRequest`, or `IRequest`/
`IRequest`, the response will use `"application/octet-stream"` by default. To specify a download
file name, use the `FileResponse` or `FileStreamResponse` response.
Use `[ResponseContentType("image/png")]` attribute for custom content type
When a request implements `IFileRequest` or inherits from `FileRequest`, it will be bound using `[FromForm]`.
File parameters fron `IFileRequest` will be automatically inferred via `IFromFile`. Apply `[DisableAntiforgery]` to skip
CSRF protection for file upload endpoints or non-browser clients
### Examples
### `GET` endpoint text file ("application/octet-stream")
```csharp
// Api URL: GET /v1/books/123/text
public class GetBookTextQuery: KeyRequest, IRequest;
```
### `POST` upload and download file with file name
```csharp
// Api URL: POST /v1/documents/word
[DisableAntiforgery]
public class ImportDocumentWordCommand : FileRequest, IRequest;
}
```
## X-Total-Count Header
To include the `X-Total-Count` header in the HTTP response, implement the `ITotalCount` interface in your response type.
This is useful for paginated endpoints or any scenario where the client needs to know the total number of items
available.
```csharp
public class GetBooksResponse : ITotalCount
{
public Book[] Items { get; init; }
private int _totalCount;
public int GetTotalCount() => _totalCount;
public void SetTotalCount(int totalCount)
{
_totalCount = totalCount;
}
}
```
When this interface is implemented, MitMediator.AutoApi will automatically include the `X-Total-Count` header in the
response, reflecting the value returned by `GetTotalCount()`
## Location header
To insert the correct ID into the Location header of a 201 Created response, implement the `IResourceKey` interface in
your response type:
```csharp
// Api URL: POST /books
public class CreateBookCommand : IRequest
{
public string Title { get; init; }
}
// Location Header: /books/{BookId}
public class CreatedBookResponse : IResourceKey
{
public int BookId { get; private set; }
public string Title { get; private set; }
public string GetResourceKey() => BookId.ToString();
}
```
If you do not implement the `IResourceKey` interface, the Location header will default to the format `/books/{key}`,
where `{key}` is a placeholder string
# Auto client HttpMediator
You can reuse your `IRequest` types to seamlessly send HTTP requests to a server-side API using `HttpMediator`
`HttpMediator` supports `IClientPipelineBehavior` for middleware-like extensibility, and
`IHttpHeaderInjector` for injecting custom headers per request
### Sample usage:
```csharp
var mediator = new HttpMediator(serviceProvider, baseUrl: "https://api.example.com");
var response = await mediator.SendAsync(new MyRequest(), cancellationToken);
```
### More
```csharp
var baseApiUrl = "api";
var httpClientName = "baseHttpClient";
var serviceCollection = new ServiceCollection();
serviceCollection.AddHttpClient(httpClientName, client => { client.BaseAddress = new Uri("https://localhost:7127/"); });
serviceCollection.AddScoped(typeof(IHttpHeaderInjector<,>), typeof(AuthorizationHeaderInjection<,>));
serviceCollection.AddScoped(c => new HttpMediator(c, baseApiUrl, httpClientName));
var provider = serviceCollection.BuildServiceProvider();
var httpMediator = provider.GetRequiredService();
var query = new GetBookQuery();
query.SetKey(12);
var data = await httpMediator.SendAsync(query, CancellationToken.None);
public class AuthorizationHeaderInjection : IHttpHeaderInjector where TRequest : IRequest
{
public ValueTask<(string, string)?> GetHeadersAsync(CancellationToken cancellationToken)
{
var result = ("Authorization", "Bearer token");
return ValueTask.FromResult<(string, string)?>(result);
}
}
```
## See [samples](./samples)
## MIT [License](LICENSE)