How to get rid of code duplication for Minimal API's - c#

I am trying to learn to effectively use Minimal API's. How can I reduce duplicate codes, how to create base Endpoint class and do the most job there?
Especially, how to use Mediatr from a static reference and not put in every methods parameter?
My endpoint:
public static class ArticleEndpoints
{
public static void MapArticleEndpoints(this WebApplication app)
{
var articles = app.MapGroup("Articles");
articles.MapPost(nameof(CreateAsync), CreateAsync)
.AddEndpointFilter<ValidationFilter<CreateArticleCommand>>()
.WithName(nameof(CreateAsync))
.Produces(StatusCodes.Status201Created, typeof(Guid))
.ProducesValidationProblem()
.WithOpenApi();
articles.MapPut(nameof(AddTagToArticleAsync), AddTagToArticleAsync)
.AddEndpointFilter<ValidationFilter<AddTagToArticleCommand>>()
.WithName(nameof(AddTagToArticleAsync))
.Produces(StatusCodes.Status200OK, typeof(ArticleGetByIdDto))
.ProducesValidationProblem()
.WithOpenApi();
articles.MapGet(nameof(GetAllAsync), GetAllAsync)
.WithName(nameof(GetAllAsync))
.Produces(StatusCodes.Status200OK, typeof(IReadOnlyCollection<ArticleGetAllDto>))
.WithOpenApi();
articles.MapGet(nameof(GetByIdAsync), GetByIdAsync)
.WithName(nameof(GetByIdAsync))
.Produces(StatusCodes.Status200OK, typeof(TagGetByIdDto))
.WithOpenApi();
}
public static async Task<IResult> CreateAsync([FromBody] CreateArticleCommand command, IMediator mediator, CancellationToken cancellationToken)
{
var id = await mediator.Send(command, cancellationToken);
return Results.Created(nameof(CreateAsync), new { id });
}
public static async Task<IResult> AddTagToArticleAsync([FromBody] AddTagToArticleCommand command, IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(command, cancellationToken);
return Results.Ok(result);
}
public static async Task<IResult> GetAllAsync(IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAllArticlesQuery(), cancellationToken);
return Results.Ok(result);
}
public static async Task<IResult> GetByIdAsync([FromBody] GetArticleByIdIncludeTagsQuery query, IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(query, cancellationToken);
return Results.Ok(result);
}
}

Related

.NET 7 create custom Output Cache Policy

I am currently implementing some of the new features of the .NET 7 framework. One part is related to the new caching mechanism.
At startup I have configured the cache:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
builder.Services.AddCors();
builder.Services.AddOutputCache();
//builder.Services.InitializeApplication(builder.Configuration);
var app = builder.Build();
app.UseOutputCache();
app.UseCors();
//app.UseAuthentication();
//app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapGroup("reports-api/requests")
.MapRequestsApi();
app.MapHealthChecks("/healthcheck");
app.Run();
The route group looks like this:
public static class RequestsEndpoint
{
public static RouteGroupBuilder MapRequestsApi(this RouteGroupBuilder group)
{
group.MapGet("/all", async (IMediator mediator) =>
{
await Task.Delay(2000);
return await mediator.Send(new RetrieveRequestsQuery());
})
.CacheOutput(x => x.Tag("requests"));
group.MapPost("/add", async (string details, IMediator mediator, IOutputCacheStore store) =>
{
await mediator.Send(new AddRequestCommand { Details = details });
await store.EvictByTagAsync("requests", CancellationToken.None);
});
group.MapGet("/{id}", async (Guid requestId, IMediator mediator) =>
{
await mediator.Send(new RetrieveRequestDetailsQuery()
{
Id = requestId
});
});
//group.MapPut("/{id}", UpdateRequest);
//group.MapDelete("/{id}", DeleteRequest);
return group;
}
}
The cache mechanism with tags works fine when I want to serve the requests list from the cache or when I want to evict the cache (new item in the list)
However, I'd like to have some sort of cache per item - when I retrieve the request based on an ID, I'd like to only cache those values until I have the details changed by a PUT or PATCH.
What I could do is to register the
group.MapGet("/{id}") endpoint with the same tag ("requests"). However, if there is an update I am forced to evict everything. That's not ideal.
I was looking at this video (Output Cache Microsoft) and they are looking at something called DefaultOutputCachePolicy
I can only find in .NET 7 the interface: IOutputCachePolicy which is asking me to implement the following method:
public class ByIdCachePolicy : IOutputCachePolicy
{
public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();
public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();
public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();
}
There is no clue in the docs about how this needs to be implemented, and I couldn't find the code of the Default Policy. How are we supposed to implement this interface?
I managed to achieve what I want with a workaround:
group.MapGet("/{id}", async (Guid requestId, IMediator mediator) =>
{
await Task.Delay(2000);
return await mediator.Send(new RetrieveRequestDetailsQuery
{
Id = requestId
});
})
.CacheOutput(cachePolicyBuilder => cachePolicyBuilder.With(context =>
{
if (context.HttpContext.Request.QueryString.Value != null)
{
var queryParams = HttpUtility.ParseQueryString(context.HttpContext.Request.QueryString.Value);
context.Tags.Add(queryParams["requestId"]!);
}
return true;
}));
group.MapPut("/{id}",
async (Guid id, IOutputCacheStore store, CancellationToken ct) =>
await store.EvictByTagAsync(id.ToString(), CT));
Using the cachePolicyBuilder With method, I got access to the cache context and could look into the query string & add the custom tag (by ID).
However, this is not straightforward since the With function allows you to filter the cached requests. What am I doing here is always returning true but, also adding the custom tag.
Not ideal, but it works...
Edit
After looking a bit more into it I ended up using the following policy:
public class ByIdCachePolicy : IOutputCachePolicy
{
public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation)
{
var idRoute = context.HttpContext.Request.RouteValues["id"];
if (idRoute == null)
{
return ValueTask.CompletedTask;
}
context.Tags.Add(idRoute.ToString()!);
return ValueTask.CompletedTask;
}
public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation)=> ValueTask.CompletedTask;
public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation) => ValueTask.CompletedTask;
}
like this:
group.MapGet("/{id}", async (Guid id, IMediator mediator) =>
{
await Task.Delay(2000);
return await mediator.Send(new RetrieveRequestDetailsQuery
{
Id = id
});
}).CacheOutput(x => x.AddPolicy<ByIdCachePolicy>());

what pattern can be used here to preview this result?

I have a method to make an order,
public async Task<bool> Order(Request request)
{
// Each step does different things.
await Step1(request);
await Step2(request);
await Step3(request);
await Step4(request);
...
await StepN(request);
}
public async Task<bool> Step1(Request request)
{
var amount1 = await changeSomething1(request);
await Pay1(amount1);
}
public async Task<bool> Step2(Request request)
{
var amount2 = await changeSomething2(request);
await Pay2(amount2);
}
public async Task<bool> StepX(Request request)
{
var amountX = await changeSomethingX(request);
await PayX(amountX);
}
Now I need to preview the Order without any PayX(amount) is called. I don't want to add a boolen parameter to skip it, like the following code, which looks pretty ugly.
public async Task<bool> Order(Request request, bool preview = false)
{
await Step1(request, preview);
await Step2(request, preview);
await Step3(request, preview);
await Step4(request, preview);
....
await StepN(request, preview);
}
public async Task<bool> StepX(Request request, bool preview = false)
{
var amountX = await changeSomethingX(request);
if(!preview) await PayX(amountX);
}
What pattern can be applied here? Thanks a lot.
It seems to me that you need some middleware-like pattern (Just like middlewares in Asp.Net for example). That's my proposal:
First create An OrderMiddleware class that encapsulates your 2 methods, Step and Pay. In this case we pass step and pay as delegates to the constructor to achieve maximum flexibility
public delegate Task<int> StepDelegate(Request request);
public delegate Task PayDelegate(int amount);
public class OrderMiddleware
{
// Private fields
private readonly StepDelegate _step;
private readonly PayDelegate _pay;
// Initialization
public OrderMiddleware(StepDelegate step, PayDelegate pay)
{
_step = step;
_pay = pay;
}
// Public
public async Task Order(Request request, bool preview)
{
var amount = await _step.Invoke(request);
if (!preview)
await _pay.Invoke(amount);
}
}
Then you need a class to handle a list of OrderMiddlewares that represent your complete pipeline.
public class OrderPipeline
{
// Private fields
private readonly List<OrderMiddleware> _orderMiddlewares;
// Initialization
public OrderPipeline()
{
_orderMiddlewares = new()
{
new(Step1, Pay1),
new(Step2, Pay2)
};
}
// Order Handling
public async Task Order(Request request, bool preview = false)
{
foreach(var middleware in _orderMiddlewares)
await middleware.Order(request, preview);
}
// Middlewares
public async Task Step1(Request request)
{
var amount1 = await changeSomething1(request);
await Pay1(amount1);
}
public async Task Step2(Request request)
{
var amount2 = await changeSomething2(request);
await Pay2(amount2);
}
}
In this way you order method can work with a list of middlwares.
Just few notes:
If you need to pass other parameters to every middleware, in addiction to the preview bool, consider to create a OrderConfiguration class that encapsulates all these data, and pass it instead. In that way the signature remains clean and you do not need to do any refactoring
Maybe you want to separate you middleware registration logic from your OrderPipeline class in order to not violate the open-closed principle:
public class OrderPipeline
{
// Private fields
private readonly List<OrderMiddleware> _orderMiddlewares = new();
// Order Handling
public async Task Order(Request request, bool preview = false)
{
foreach(var middleware in _orderMiddlewares)
await middleware.Order(request, preview);
}
public void AddMiddleware(OrderMiddleware orderMiddleware)
{
_orderMiddlewares.Add(orderMiddleware);
}
}

ASP.NET Core and MediatR: Send a request from a handler?

Is it possible to send a request from a handler?
I have some business logic like: "When user is created - an empty repository must be created for him".
So I have two requests and two handlers:
public class AddUser : IRequest<User> { ... }
public class AddUserHandler : IRequestHandler<AddUser, User> {
private readonly IMediator _mediator;
private readonly IUserRepository _userRepository;
public AddUserHandler(IMediator mediator, IUserRepository userRepository) {
_mediator = mediator;
_userRepository = userRepository;
}
public async Task<User> Handler(AddUser request, CancellationToken token) {
// some logic is here
var user = _userRepository.Add(request.User);
// create a repository
await _mediator.Send(new AddUserRepository(user.Id), token); // EXCEPTION!!!
return user;
}
}
The exception is:
Cannot access a disposed object. Object name: 'IServiceProvider'.
at
Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type
serviceType) at
MediatR.ServiceFactoryExtensions.GetInstances[T](ServiceFactory
factory) at
MediatR.Internal.RequestHandlerWrapperImpl2.Handle(IRequest1
request, CancellationToken cancellationToken, ServiceFactory
serviceFactory) at MediatR.Mediator.Send[TResponse](IRequest`1
request, CancellationToken cancellationToken) at
Domain.UseCases.Messages.Users.AddUserHandler.Handle(AddUser
request, CancellationToken cancellationToken) in
Domain.UseCases\Messages\Users\AddUser.cs:line 43
This is possible but this code is wrong!
await _mediator.Send(new AddUserRepository(user.Id), token); //Wrong
If you use AddUserRepository, try this
public class AddUserRepository: IRequest<...> { ... } //Not best practise
await _mediator.Send(new AddUserRepository());
You should investigate CQRS(Command Query Responsibility Segregation). Repositories are not suitable for this use.

How to identify a ConcurrentQueue<Func<CancellationToken, Task>> item?

from this sample: here
Using the following queue.
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
public ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Calling it from within an api controller but need to be able to check on its status.
Controller:
var guid = Guid.NewGuid().ToString();
queue.QueueBackgroundWorkItem(async token=>{
await Task.Delay(TimeSpan.FromSeconds(60), token);
});
How do I associate the guid to the task and check on it later?

.Net Core Queue Background Tasks

Slender answered my original question about what happens to fire and forget, after the HTTP Response is sent, but Now I'm left with the question how to properly queue background tasks
EDIT
As we all know Async void is generally bad, except for in the case when it comes to event handlers, I would like to execute some background logic without have to have the client wait. My original Idea was to use Fire and Forget
Say I have an event:
public event EventHandler LongRunningTask;
And then someone subscribes a fire and forget task:
LongRunningTask += async(s, e) => { await LongNetworkOperation;};
the web api method is call:
[HttpGet]
public async IActionResult GetTask()
{
LongRunningTask?.Invoke(this, EventArgs.Empty);
return Ok();
}
But If I do this my long running task isn't guaranteed to finish, How can I handle running background task without affect the time the time it take to make my request (e.g I don't want to wait for the task to finish first)?
.NET Core 2.1 has an IHostedService, which will safely run tasks in the background. I've found an example in the documentation for QueuedHostedService which I've modified to use the BackgroundService.
public class QueuedHostedService : BackgroundService
{
private Task _backgroundTask;
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (false == stoppingToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
this._logger.LogError(ex, $"Error occurred executing {nameof(workItem)}.");
}
}
}
}
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Now we can safely queue up tasks in the background without affecting the time it takes to respond to a request.
Just wanted to add some additional notes to #johnny5 answer. Right now you can use https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/ instead of ConcurrentQueue with Semaphore.
The code will be something like this:
public class HostedService: BackgroundService
{
private readonly ILogger _logger;
private readonly ChannelReader<Stream> _channel;
public HostedService(
ILogger logger,
ChannelReader<Stream> channel)
{
_logger = logger;
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var item in _channel.ReadAllAsync(cancellationToken))
{
try
{
// do your work with data
}
catch (Exception e)
{
_logger.Error(e, "An unhandled exception occured");
}
}
}
}
[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
private readonly ChannelWriter<Stream> _channel;
public UploadController (
ChannelWriter<Stream> channel)
{
_channel = channel;
}
public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
{
var ms = new MemoryStream();
await fileInfo.FormFile.CopyToAsync(ms);
await _channel.WriteAsync(ms);
return Ok();
}
}

Categories