.NET 7 create custom Output Cache Policy - c#

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>());

Related

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

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);
}
}

Fluent validation divide business validation from auth validaton

I'm using ASP, CQRS + MediatR and fluent validation. I want to implement user role validation, but I don't want to mix it with business logic validation. Do you have any idea how to implement this?
I mean a specific validator must be executed for a specific request.
Something tells me the solution lies in IEnumerable< IValidator>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToArray();
if (failures.Any())
{
var errors = failures
.Select(x => new Error(x.ErrorMessage, x.ErrorCode))
.ToArray();
throw new ValidationException(errors);
}
}
return await next();
}
}
I see your concern, I also found myself in this situation. I wanted to separate my validators from handlers while also keeping them in the domain/business project. Also I didn't want to throw exceptions just to handle bad request or any other custom business exception.
You have the right idea by
I mean a specific validator must be executed for a specific request
For this, you need to set up a mediator pipeline, so for every Command you can find the appropriate the appropriate validator, validate and decide whether to execute the command or return a failed result.
First, create an interface(although not necessary but it is how I did it) of ICommand like this:
public interface ICommand<TResponse>: IRequest<TResponse>
{
}
And, ICommandHandler like:
public interface ICommandHandler<in TCommand, TResponse>: IRequestHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
}
This way we can only apply validation to commands. Instead of iheriting IRequest<MyOutputDTO> and IRequestHandler<MyCommand, MyOutputDTO> you inherit from ICommand and ICommandHandler.
Now create a ValidationBehaviour for the mediator as we agreed before.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (!_validators.Any())
return await next();
var validationContext = new ValidationContext<TRequest>(request);
var errors = (await Task.WhenAll(_validators
.Select(async x => await x.ValidateAsync(validationContext))))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.Select(x => x.CustomState)
.Cast<TResponse>();
//TResponse should be of type Result<T>
if (errors.Any())
return errors.First();
try
{
return await next();
}
catch(Exception e)
{
//most likely internal server error
//better retain error as an inner exception for debugging
//but also return that an error occurred
return Result<TResponse>.Failure(new InternalServerException(e));
}
}
}
This code simply, excepts all the validators in the constructor, because you register all your validator from assembly for your DI container to inject them.
It waits for all validations to validate async(because my validations mostly require calls to db itself such as getting user roles etc).
Then check for errors and return the error(here I have created a DTO to wrap my error and value to get consistent results).
If there were no errors simply let the handler do it's work return await next();
Now you have to register this pipeline behavior and all the validators.
I use autofac so I can do it easily by
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(typeof(IValidator<>))
.AsImplementedInterfaces();
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>),
typeof(INotificationHandler<>),
typeof(IPipelineBehavior<,>)
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
If you use Microsoft DI, you can:
services.AddMediatR(typeof(Application.AssemblyReference).Assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly); //to add validators
Example usage:
My generic DTO Wrapper
public class Result<T>: IResult<T>
{
public Result(T? value, bool isSuccess, Exception? error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public bool IsSuccess { get; set; }
public T? Value { get; set; }
public Exception? Error { get; set; }
public static Result<T> Success(T value) => new (value, true, null);
public static Result<T> Failure(Exception error) => new (default, false, error);
}
A sample Command:
public record CreateNewRecordCommand(int UserId, string record) : ICommand<Result<bool>>;
Validator for it:
public class CreateNewRecordCommandValidator : AbstractValidator<CreateNewRecordCommand>
{
public CreateNewVoucherCommandValidator(DbContext _context, IMediator mediator) //will be injected by out DI container
{
RuleFor(x => x.record)
.NotEmpty()
.WithState(x => Result<bool>.Failure(new Exception("Empty record")));
//.WithName("record") if your validation a property in array or something and can't find appropriate property name
RuleFor(x => x.UserId)
.MustAsync(async(id, cToken) =>
{
//var roles = await mediator.send(new GetUserRolesQuery(id, cToken));
//var roles = (await context.Set<User>.FirstAsync(user => user.id == id)).roles
//return roles.Contains(MyRolesEnum.CanCreateRecordRole);
}
)
.WithState(x => Result<bool>.Failure(new MyCustomForbiddenRequestException(id)))
}
}
This way you always get a result object, you can check if error is null or !IsSuccess and then create a custom HandleResult(result) method in your Controller base which can switch on the exception to return BadReuqestObjectResult(result) or ForbiddenObjectResult(result).
If you prefer to throw, catch and handle the exceptions in the pipeline or you wan't non-async implementation, read this https://code-maze.com/cqrs-mediatr-fluentvalidation/
This way all your validations are very far from your handler while maintaining consistent results.
I think that your initial approach its right. When you say that you want to keep the auth validations apart from the other business validation, do you mean like returning a http error like 403 and 401 right?
If thats the case try marking the auth validations with and interface to identify they, and do not run all the validations at once. Search first in the collection for a validation with that interface, and if it fails send a custom exception that you can identity in a IActionFilter to set the wanted result. This code does not do that exactly but you can make an idea.
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
private ISystemLogger _logger;
public HttpResponseExceptionFilter()
{
}
public int Order { get; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context) { }
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception is PipelineValidationException exception)
{
context.Result = new ObjectResult(new Response(false, exception.ValidationErrors.FirstOrDefault()?.ErrorMessage ?? I18n.UnknownError));
context.ExceptionHandled = true;
}
else if (context.Exception != null)
{
_logger ??= (ISystemLogger)context.HttpContext.RequestServices.GetService(typeof(ISystemLogger));
_logger?.LogException(this, context.Exception, methodName: context.HttpContext.Request.Method);
context.Result = new ObjectResult(new Response(false, I18n.UnknownError));
context.ExceptionHandled = true;
}
}
}

Azure Functions Runtime v3 Middleware

Is there a way to access the request and response object in an azure middle ware.
Using a tutorial for a logging middleware I already got this far:
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
// Code before function execution here
await next(context);
// Code after function execution here
}
catch (Exception ex)
{
var log = context.GetLogger<ExceptionLoggingMiddleware>();
log.LogWarning(ex, string.Empty);
}
}
}
but I want to access the response and request object too. Like status code, body parameters, query parameters etc. Is this possible?
While there is no direct way to do this, but there is a workaround for accessing HttpRequestData (Not the best solution but it should work until there is a fix.):
public static class FunctionContextExtensions
{
public static HttpRequestData GetHttpRequestData(this FunctionContext functionContext)
{
try
{
KeyValuePair<Type, object> keyValuePair = functionContext.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
object functionBindingsFeature = keyValuePair.Value;
Type type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
catch
{
return null;
}
}
}
And you can use it like this:
public class CustomMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
HttpRequestData httpRequestData = context.GetHttpRequestData();
// do something with httpRequestData
await next(context);
}
}
Check out this for more details.
For Http Response, there is no workaround AFAIK. Further, check out GH Issue#530, that says that documentation for this will be added soon. This capability looks like a popular demand and expected to be fixed soon (at the time of writing this).

Delete item from database with C# using HttpDelete

I am trying to delete a row from my database based on a Primary Key ID field. When I try to do it, all of the code executes without any errors, but the item doesn't get deleted from the database.
I'm passing the item to my C# backend from an angular frontend call like this:
delete(customerId: number, materialCustomerId: number): Observable<Response> {
return this.http.delete(`${this.getBaseUrl()}/${customerId}/materialcustomer/${materialCustomerId}`).catch(error => this.handleError(error));
}
It then hits my controller method:
[HttpDelete]
[Route("{customerId}/materialcustomer/{materialCustomerId}")]
[AccessControl(Securable.Customer, Permissions.Delete, Permissions.Execute)]
public async Task Delete(int customerId, int materialCustomerId)
{
await _materialCustomerDeleter.DeleteAsync(MaterialCustomer.CreateWithOnlyId(materialCustomerId), HttpContext.RequestAborted);
}
Manipulator method:
public async Task DeleteAsync(MaterialCustomer model, CancellationToken cancellationToken = default(CancellationToken))
{
if (model == null)
throw new ArgumentNullException(nameof(model));
await _materialCustomerDeleter.DeleteAsync(new TblMaterialCustomer { MaterialCustomerId = model.MaterialCustomerId }, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
await _customerWriter.CommitAsync(cancellationToken);
}
and finally, my repository method:
public async Task DeleteAsync(TblMaterialCustomer entity, CancellationToken cancellationToken = new CancellationToken())
{
var item =
await _context.TblMaterialCustomer.FirstOrDefaultAsync(i => i.MaterialCustomerId == entity.MaterialCustomerId, cancellationToken);
if (item == null || cancellationToken.IsCancellationRequested)
return;
_context.SetModified(item);
}
What am I missing?
Assuming that await _customerWriter.CommitAsync(cancellationToken); calls through to the same DbContext instance and calls method SaveAsync you should re-write the delete method like this:
public void Delete(TblMaterialCustomer entity)
{
_context.TblMaterialCustomer.Remove(entity);
}
Also it would probably be a good idea to return a result from the WebAPI call, although it is not required, like OK/200.
public async Task<IHttpActionResult> Delete(int customerId, int materialCustomerId)
{
await _materialCustomerDeleter.DeleteAsync(MaterialCustomer.CreateWithOnlyId(materialCustomerId), HttpContext.RequestAborted);
return Ok();
}

How do I return the TaskStatus when making a synchronous call asynchronous?

I have a old data access library that I need to use in my ASP.NET MVC application but I'm having trouble bringing it into the MVC world where many server-side operations are expected to be asynchronous. My data access library looks a like this:
public class MyOldDAL
{
public TaskResult CreateUser(string userName)
{
try
{
// Do user creation
return new TaskResult { Success = true, Message = "success" };
}
catch (Exception ex)
{
return new TaskResult { Success = false, Message = ex.Message };
}
}
}
And is called by my MVC application like this:
public class MyUserStore : IUserStore<ApplicationUser>
{
private readonly MyOldDAL _dal = new MyOldDAL();
public async Task CreateAsync(ApplicationUser user)
{
await Task.Run(() => _dal.CreateUser(user.UserName));
}
}
This is fine until the method in Task.Run() fails for some reason. I'd like to be able to return TaskStatus.Faulted if TaskResult.Success is false but I can't find an example online on how to do it properly - all my Google searches turn up is how to use Task<T> which the IUserStore interface doesn't permit.
For the record, much as I'd like to I can't change MyOldDAL - it's targeting .NET 3.5 so no async or await there!
The normal way to report errors from tasks is via exceptions, so you'll just need to do that transformation yourself:
public class MyUserStore : IUserStore<ApplicationUser>
{
private readonly MyOldDAL _dal = new MyOldDAL();
public Task CreateAsync(ApplicationUser user)
{
var result = _dal.CreateUser(user.UserName);
if (result.Success)
return Task.CompletedTask;
return Task.FromException(new InvalidOperationException(result.Message));
}
}
Note that Task.Run should not be used on ASP.NET.
Note: As Stephen Cleary noticed in his answer, Task.Run should not be used on ASP.NET.
Original answer (before comments):
Your CreateAsync method should normally be like this:
public async Task<TaskResult> CreateAsync(ApplicationUser user)
{
return await Task.Run(() => _dal.CreateUser(user.UserName));
}
But if you can't return Task<TaskResult> from CreateAsync method... well, than you can't obtain TaskResult from CreateAsync by definition. In that case you can store result locally:
private TaskResult taskResult;
public async Task CreateAsync(ApplicationUser user)
{
var result = await Task.Run(() => _dal.CreateUser(user.UserName));
this.taskResult = result;
// process taskResult wherether you need
}
Or raise event with TaskResult payload, allowing client of MyUserStore to subscribe to this event:
public event EventHandler<TaskResult> TaskCompleted;
public async Task CreateAsync(ApplicationUser user)
{
var result = await Task.Run(() => _dal.CreateUser(user.UserName));
this.OnTaskCompleted(result);
}
private void OnTaskCompleted(TaskResult result)
{
this.TaskCompleted?.Invoke(this, result);
}

Categories