Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.0.2 (#2) #7

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: .NET

on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
34 changes: 34 additions & 0 deletions .github/workflows/nuget-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Publish NuGet Package
on:
push:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: '8.0.x'

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore

- name: Run tests
run: dotnet test --no-restore --verbosity normal

- name: Pack NuGet package
run: dotnet pack --configuration Release --no-build --output ./nupkg src/MediatR.MinimalApi/MediatR.MinimalApi.csproj

- name: Publish NuGet package
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: dotnet nuget push ./nupkg/*.nupkg --api-key $NUGET_API_KEY --source https://api.nuget.org/v3/index.json
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ FodyWeavers.xsd
!.vscode/extensions.json
*.code-workspace

# Rider
.idea/*

# Local History for Visual Studio Code
.history/

Expand Down
55 changes: 55 additions & 0 deletions MediatR.MinimalApi.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6E3ABF9A-47CF-4D16-A685-A627B1041DE2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatR.MinimalApi", "src\MediatR.MinimalApi\MediatR.MinimalApi.csproj", "{D3CBF6FB-1175-40D8-8585-968D22C791BC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C8C8454F-3D01-48EE-B8C6-6F59F0A89527}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatRSampleWebApplication", "samples\MediatRSampleWebApplication\MediatRSampleWebApplication.csproj", "{F4C046EC-0314-4322-9161-5D27252E748B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{4B59A04F-1DF0-4B4D-8644-2AE2A72365F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatR.MinimalApi.Tests.Unit", "test\MediatR.MinimalApi.Tests.Unit\MediatR.MinimalApi.Tests.Unit.csproj", "{D13C8577-3F9C-4DAD-A244-E65648618F06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatR.MinimalApi.Tests.Integration", "test\MediatR.MinimalApi.Tests.Integration\MediatR.MinimalApi.Tests.Integration.csproj", "{74549D05-70C3-46BC-9F8E-A659672C4968}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3CBF6FB-1175-40D8-8585-968D22C791BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3CBF6FB-1175-40D8-8585-968D22C791BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3CBF6FB-1175-40D8-8585-968D22C791BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3CBF6FB-1175-40D8-8585-968D22C791BC}.Release|Any CPU.Build.0 = Release|Any CPU
{F4C046EC-0314-4322-9161-5D27252E748B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4C046EC-0314-4322-9161-5D27252E748B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4C046EC-0314-4322-9161-5D27252E748B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4C046EC-0314-4322-9161-5D27252E748B}.Release|Any CPU.Build.0 = Release|Any CPU
{D13C8577-3F9C-4DAD-A244-E65648618F06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D13C8577-3F9C-4DAD-A244-E65648618F06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D13C8577-3F9C-4DAD-A244-E65648618F06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D13C8577-3F9C-4DAD-A244-E65648618F06}.Release|Any CPU.Build.0 = Release|Any CPU
{74549D05-70C3-46BC-9F8E-A659672C4968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74549D05-70C3-46BC-9F8E-A659672C4968}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74549D05-70C3-46BC-9F8E-A659672C4968}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74549D05-70C3-46BC-9F8E-A659672C4968}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D3CBF6FB-1175-40D8-8585-968D22C791BC} = {6E3ABF9A-47CF-4D16-A685-A627B1041DE2}
{F4C046EC-0314-4322-9161-5D27252E748B} = {C8C8454F-3D01-48EE-B8C6-6F59F0A89527}
{D13C8577-3F9C-4DAD-A244-E65648618F06} = {4B59A04F-1DF0-4B4D-8644-2AE2A72365F1}
{74549D05-70C3-46BC-9F8E-A659672C4968} = {4B59A04F-1DF0-4B4D-8644-2AE2A72365F1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {15270191-90B4-4FF0-B223-902FE00A4C4F}
EndGlobalSection
EndGlobal
Binary file added MediatR.MinimalApi.snk
Binary file not shown.
6 changes: 6 additions & 0 deletions NuGet.Config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
172 changes: 171 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,171 @@
# MediatR.MinimalApi
# MediatR.MinimalApi
![CI](https://github.com/KrzysztofPajak/MediatR.MinimalApi/workflows/.NET/badge.svg)
[![NuGet](https://img.shields.io/nuget/vpre/MediatR.MinimalApi.svg)](https://www.nuget.org/packages/MediatR.MinimalApi)

`MediatR.MinimalApi` is a NuGet package that extends the functionality of minimal APIs by allowing automatic registration of endpoints based on attributes. This library leverages the power of MediatR to simplify and streamline the process of setting up and handling API endpoints. It simplifies the integration of MediatR with Minimal API in ASP.NET Core, allowing you to easily map HTTP endpoints to MediatR handlers, thus enabling clean architecture practices in your application.

## Features

- Support for various HTTP methods (GET, POST, DELETE, PATCH, PUT) with MediatR.
- Easy configuration and usage with extension methods.
- Ability to create endpoints through automatic registration using attributes or manual registration
- Compatible with Minimal API in ASP.NET Core 8.

## Installation

You should install [MediatR with NuGet](https://www.nuget.org/packages/MediatR.MinimalApi):

Install-Package MediatR.MinimalApi

Or via the .NET Core command line interface:

dotnet add package MediatR.MinimalApi

## Usage

### Manual Registration

#### 1. Define Request and Response Models
Create your request and response models that will be handled by MediatR:

```csharp
public class YourGetRequest : IRequest<YourResponse>
{
}
public class YourGetWithQueryParamRequest : IRequest<YourResponse>
{
[FromQuery]
public int Id { get; set; }
}
public class YourPostRequest : IRequest<YourResponse>
{
[FromBody]
public Test Data { get; set; }

public class Test
{
public string Test1 { get; set; }
public string Test2 { get; set; }
}
}
public class YourPostWithQueryParamRequest : IRequest<YourResponse>
{
[FromBody]
public Test Data { get; set; }

[FromQuery]
public int Id { get; set; }

public class Test
{
public string Test1 { get; set; }
public string Test2 { get; set; }
}
}
public class YourResponse
{
public string Result { get; set; }
}

```
#### 2. Implement the MediatR Request Handler

#### 3. Configure Minimal API
Configure Minimal API to use MediatR and the extension methods from the MediatR.MinimalApi package:

```csharp
var builder = WebApplication.CreateBuilder(args);

// Register MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<YourRequestHandler>());

var app = builder.Build();

// Define endpoints
app.MapGetWithMediatR<YourGetRequest, YourResponse>("/example-get");
app.MapGetWithMediatR<YourGetWithQueryParamRequest, YourResponse>("/example-getbyid");
app.MapPostWithMediatR<YourPostRequest, YourResponse>("/example-post");
app.MapPostWithMediatR<YourPostWithQueryParamRequest, YourResponse>("/example-post-param");
app.MapDeleteWithMediatR<YourDeleteRequest, YourResponse>("/example-delete");
app.MapPatchWithMediatR<YourPatchRequest, YourResponse>("/example-patch");
app.MapPutWithMediatR<YourPutRequest, YourResponse>("/example-put");

app.Run();
```

### Automatic Registration

After installing the MediatR.MinimalApi package, you can automatically register all your Commands/Queries with MediatR by using the following code in your Program.cs:
```csharp
app.MapMediatREndpoints(typeof(Program).Assembly);
```
#### 1. Defining Commands/Queries
To ensure each endpoint is registered, you need to define your commands with the Endpoint attribute. Here’s an example:
```csharp
[Endpoint("user/create", HttpMethod.Post, "User")]
public class CreateUserCommand : IRequest<User>
```

#### 2. Endpoint Attribute
The Endpoint attribute is used to define the route, HTTP method, and tag for OpenAPI documentation. Here’s the structure of the Endpoint attribute:

```csharp
[Endpoint("route", HttpMethod.Method, "tag")]
```
- route: The route of the endpoint.
- HttpMethod.Method: The HTTP method for the endpoint (HttpMethod.Get, HttpMethod.Post, HttpMethod.Delete, HttpMethod.Patch, HttpMethod.Put).
- Tag: A tag for OpenAPI documentation.

#### 3. Authorization
If you need to secure your endpoint with authorization, you can use the `Authorize` attribute:
```csharp
[Authorize]
[Endpoint("user/create", HttpMethod.Post, "User")]
public class CreateUserCommand : IRequest<User>
```

#### 4. Endpoint Filters
If you need to apply filters to your endpoint, use the `EndpointFilter` attribute:
```csharp
[Authorize]
[EndpointFilter<CustomFilter>()]
[Endpoint("user/create", HttpMethod.Post, "User")]
public class CreateUserCommand : IRequest<User>
```
#### 5. Example
Here is a complete example of defining and registering an endpoint with authorization and a custom filter:

```csharp
[Authorize]
[EndpointFilter<CustomFilter>()]
[Endpoint("user/create", HttpMethod.Post, "User")]
public class CreateUserCommand : IRequest<User>
{
// Command properties
}

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, User>
{
public Task<User> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// Handler logic
}
}

public class CustomFilter : IEndpointFilter
{
...
}
```
In your `Program.cs`, you would register the endpoints like this:

```csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
...
app.MapMediatREndpoints(typeof(Program).Assembly);

app.Run();
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using MediatR;
using MediatR.MinimalApi.Attributes;
using MediatRSampleWebApplication.EndpointFilters;
using System.ComponentModel.DataAnnotations;
using WebApplication.Models;
using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;

namespace WebApplication.Commands.Users;

[EndpointFilter<ValidationFilter>()]
[Endpoint("user/create", HttpMethod.Post, "User")]
public class CreateUserCommand : IRequest<User>
{
[MinLength(10)]
public string FirstName { get; set; }

public string LastName { get; set; }

public string Email { get; set; }

public CreateUserCommand(string firstName, string lastName, string email)
{
FirstName = firstName;
LastName = lastName;
Email = email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using MediatR;
using WebApplication.Models;

namespace WebApplication.Commands.Users;

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, User>
{
private readonly ILogger<CreateUserCommandHandler> _logger;

public CreateUserCommandHandler(ILogger<CreateUserCommandHandler> logger)
{
_logger = logger;
}

public async Task<User> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("Creating user with first name {FirstName}, last name {LastName}, and email {Email}", request.FirstName, request.LastName, request.Email);

var user = new User(request.FirstName, request.LastName, request.Email);

return await Task.FromResult(user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using MediatR;
using MediatR.MinimalApi.Attributes;
using Microsoft.AspNetCore.Mvc;
using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;

namespace WebApplication.Commands.Users;

[Endpoint("user/Delete/{Id}", HttpMethod.Delete, "User")]
public record DeleteUserCommand([FromRoute] Guid Id) : IRequest<bool>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using MediatR;

namespace WebApplication.Commands.Users;

public class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand, bool>
{
private readonly ILogger<DeleteUserCommandHandler> _logger;

public DeleteUserCommandHandler(ILogger<DeleteUserCommandHandler> logger)
{
_logger = logger;
}

public async Task<bool> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("Delete user with Id {Id}", request.Id);
return await Task.FromResult(true);
}
}
Loading
Loading