I'm registering my FluentValidation validators as follows:
services.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<CustomRequestValidator>());
How can I get a handle of any validation errors to log them via my logging implementation, e.g. Serilog?
For users stumbling into this problem (like me) I'd like to provide the solution I found.
The actual problem is that ASP.NET Core, and APIs in particular, make Model State errors automatically trigger a 400 error response, which doesn't log the validation errors.
Below that same documentation they include instructions on how to enable automatic logging for that feature which I believe should be the correct solution to this problem.
To jump onto what #mdarefull said, I did the following:
public void ConfigureServices(IServiceCollection services)
{
// Possible other service configurations
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (!context.ModelState.IsValid)
{
LogAutomaticBadRequest(context);
}
return new BadRequestObjectResult(context.ModelState);
};
});
// Possible other service configurations
}
The LogAutomaticBadRequest method is as follows:
private static void LogAutomaticBadRequest(ActionContext context)
{
// Setup logger from DI - as explained in https://github.com/dotnet/AspNetCore.Docs/issues/12157
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(context.ActionDescriptor.DisplayName);
// Get error messages
var errorMessages = string.Join(" | ", context.ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
var request = context.HttpContext.Request;
// Use whatever logging information you want
logger.LogError("Automatic Bad Request occurred." +
$"{System.Environment.NewLine}Error(s): {errorMessages}" +
$"{System.Environment.NewLine}|{request.Method}| Full URL: {request.Path}{request.QueryString}");
}
Related
I am trying out the new MassTransit IJobConsumer implementation, and although I've tried to follow the documentation, the JobConsumer I have written is never being run/hit.
I have:
created the JobConsumer which has a run method that runs the code I need it to
public class CalculationStartRunJobConsumer : IJobConsumer<ICalculationStartRun>
{
private readonly ICalculationRunQueue runQueue;
public CalculationStartRunJobConsumer(ICalculationRunQueue runQueue)
{
this.runQueue = runQueue;
}
public Task Run(JobContext<ICalculationStartRun> context)
{
return Task.Run(
() =>
{
var longRunningJob = new LongRunningJob<ICalculationStartRun>
{
Job = context.Job,
CancellationToken = context.CancellationToken,
JobId = context.JobId,
};
runQueue.StartSpecial(longRunningJob);
},
context.CancellationToken);
}
}
I have registered that consumer trying both ConnectReceiveEndpoint and AddConsumer
Configured the ServiceInstance as shown in the documentation
services.AddMassTransit(busRegistrationConfigurator =>
{
// TODO: Get rid of this ugly if statement.
if (consumerTypes != null)
{
foreach (var consumerType in consumerTypes)
{
busRegistrationConfigurator.AddConsumer(consumerType);
}
}
if(requestClientType != null)
{
busRegistrationConfigurator.AddRequestClient(requestClientType);
}
busRegistrationConfigurator.UsingRabbitMq((context, cfg) =>
{
cfg.UseNewtonsoftJsonSerializer();
cfg.UseNewtonsoftJsonDeserializer();
cfg.ConfigureNewtonsoftJsonSerializer(settings =>
{
// The serializer by default omits fields that are set to their default value, but this causes unintended effects
settings.NullValueHandling = NullValueHandling.Include;
settings.DefaultValueHandling = DefaultValueHandling.Include;
return settings;
});
cfg.Host(
messagingHostInfo.HostAddress,
hostConfigurator =>
{
hostConfigurator.Username(messagingHostInfo.UserName);
hostConfigurator.Password(messagingHostInfo.Password);
});
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(serviceCfg =>
{
serviceCfg.FinalizeCompleted = true;
});
instance.ConfigureEndpoints(context);
});
});
});
Seen that the queue for the job does appear in the queue for RabbitMQ
When I call .Send to send a message to that queue, it does not activate the Run method on the JobConsumer.
public async Task Send<T>(string queueName, T message) where T : class
{
var endpointUri = GetEndpointUri(messagingHostInfo.HostAddress, queueName);
var sendEndpoint = await bus.GetSendEndpoint(endpointUri);
await sendEndpoint.Send(message);
}
Can anyone help?
Software
MassTransit 8.0.2
MassTransit.RabbitMq 8.0.2
MassTransit.NewtonsoftJson 8.0.2
.NET6
Using in-memory for JobConsumer
The setup of any type of repository for long running jobs is missing. We needed to either:
explicitly specify that it was using InMemory (missing from the docs)
Setup saga repositories using e.g. EF Core.
As recommended by MassTransit, we went with the option of setting up saga repositories by implementing databases and interacting with them using EF Core.
In ASP.NET Core 5 I had a custom Action Result as follows:
public class ErrorResult : ActionResult {
private readonly IList<Error> _errors;
public ErrorResult(IList<Error> errors) {
_errors = errors;
}
public override async Task ExecuteResultAsync(ActionContext context) {
// Code that creates Response
await result.ExecuteResultAsync(context);
}
}
Then on a Controller action I would have:
return new ErrorResult(errors);
How to do something similar in NET 6 Minimal APIs?
I have been looking at it and I think I should implement IResult.
But I am not sure if that is the solution or how to do it.
I have recently been playing around with minimal APIs and and working on global exception handling. Here is what I have come up with so far.
Create a class implementation of IResult
Create a constructor which will take an argument of the details you want going into your IResult response. APIErrorDetails is a custom implementation of mine similar to what you'd see in ProblemDetails in MVC. Method implementation is open to whatever your requirements are.
public class ExceptionAllResult : IResult
{
private readonly ApiErrorDetails _details;
public ExceptionAllResult(ApiErrorDetails details)
{
_details = details;
}
public async Task ExecuteAsync(HttpContext httpContext)
{
var jsonDetails = JsonSerializer.Serialize(_details);
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(jsonDetails);
httpContext.Response.StatusCode = _details.StatusCode;
await httpContext.Response.WriteAsync(jsonDetails);
}
}
Return result in your exception handling middleware in your Program.cs file.
app.UseExceptionHandler(
x =>
{
x.Run(
async context =>
{
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-6.0
var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>();
// Whatever you want for null handling
if (exceptionFeature is null) throw new Exception();
// My result service for creating my API details from the HTTP context and exception. This returns the Result class seen in the code snippet above
var result = resultService.GetErrorResponse(exceptionFeature.Error, context);
await result.ExecuteAsync(context); // returns the custom result
});
}
);
If you still want to use MVC (Model-View-Controller), you still can use Custom ActionResult.
If you just want to use Minimal APIs to do the response, then you have to implement IResult, Task<IResult> or ValueTask<IResult>.
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
The following example uses the built-in result types to customize the response:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
You can find more IResult implementation samples here: https://github.com/dotnet/aspnetcore/tree/main/src/Http/Http.Results/src
Link: Minimal APIs overview | Microsoft Docs
I am trying to find an elegant way of logging every request and response in my Web API using Filters in Asp.net Core 3.1 rather than have them in each action and each controller.
Haven't found a nice solution that seems performable well to deploy in production.
I've been trying to do something like this (below) but no much success.
Any other suggestion would be appreciated.
public class LogFilter : IAsyncActionFilter
{
private readonly ILogger _logger;
public LogFilter(ILogger logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var requestBodyData = context.ActionArguments["request"];
var responseBodyData = "";//how to get the response result
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Request Body: {requestBodyData}");
await next();
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseBodyData}");
}
}
I think logging the response should be done in debugging mode only and really can be done at your service API (by using DI interception). That way you don't need to use IActionFilter which actually can provide you only a wrapper IActionResult which wraps the raw value from the action method (which is usually the result returned from your service API). Note that at the phase of action execution (starting & ending can be intercepted by using IActionFilter or IAsyncActionFilter), the HttpContext.Response may have not been fully written (because there are next phases that may write more data to it). So you cannot read the full response there. But here I suppose you mean reading the action result (later I'll show you how to read the actual full response body in a correct phase). When it comes to IActionResult, you have various kinds of IActionResult including custom ones. So it's hard to have a general solution to read the raw wrapped data (which may not even be exposed in some custom implementations). That means you need to target some specific well-known action results to handle it correctly. Here I introduce code to read JsonResult as an example:
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var requestBodyData = context.ActionArguments["request"];
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Request Body: {requestBodyData}");
var actionExecutedContext = await next();
var responseBodyData = "not supported result";
//sample for JsonResult
if(actionExecutedContext.Result is JsonResult jsonResult){
responseBodyData = JsonSerializer.Serialize(jsonResult.Value);
}
//check for other kinds of IActionResult if any ...
//...
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseBodyData}");
}
IActionResult has a method called ExecuteResultAsync which can trigger the next processing phase (result execution). That's when the action result is fully written to the HttpContext.Response. So you can try creating a dummy pipeline (starting with a dummy ActionContext) on which to execute the action result and get the final data written to the response body. However that's what I can imagine in theory. It would be very complicated to go that way. Instead you can just use a custom IResultFilter or IAsyncResultFilter to try getting the response body there. Now there is one issue, the default HttpContext.Response.Body is an HttpResponseStream which does not support reading & seeking at all (CanRead & CanSeek are false), we can only write to that kind of stream. So there is a hacky way to help us mock in a readable stream (such as MemoryStream) before running the code that executes the result. After that we swap out the readable stream and swap back the original HttpResponseStream in after copying data from the readable stream to that stream. Here is an extension method to help achieve that:
public static class ResponseBodyCloningHttpContextExtensions
{
public static async Task<Stream> CloneBodyAsync(this HttpContext context, Func<Task> writeBody)
{
var readableStream = new MemoryStream();
var originalBody = context.Response.Body;
context.Response.Body = readableStream;
try
{
await writeBody();
readableStream.Position = 0;
await readableStream.CopyToAsync(originalBody);
readableStream.Position = 0;
}
finally
{
context.Response.Body = originalBody;
}
return readableStream;
}
}
Now we can use that extension method in an IAsyncResultFilter like this:
//this logs the result only, to write the log entry for starting/beginning the action
//you can rely on the IAsyncActionFilter as how you use it.
public class LoggingAsyncResultFilterAttribute : Attribute, IAsyncResultFilter
{
//missing code to inject _logger here ...
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var readableStream = await context.HttpContext.CloneBodyAsync(() => next());
//suppose the response body contains text-based content
using (var sr = new StreamReader(readableStream))
{
var responseText = await sr.ReadToEndAsync();
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseText}");
}
}
}
You can also use an IAsyncResourceFilter instead, which can capture result written by IExceptionFilter. Or maybe the best, use an IAsyncAlwaysRunResultFilter which can capture the result in all cases.
I assume that you know how to register IAsyncActionFilter so you should know how to register IAsyncResultFilter as well as other kinds of filter. It's just the same.
starting with dotnet 6 asp has HTTP logging built in. Microsoft has taken into account redacting information and other important concepts that need to be considered when logging requests.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
/* enabled HttpLogging with this line */
app.UseHttpLogging();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.MapGet("/", () => "Hello World!");
app.Run();
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging/?view=aspnetcore-6.0#enabling-http-logging
I have a scenario where I call an api in one of my handlers and that Api can go down for like 6 hours per month. Therefore, I designed a retry logic with 1sec retry, 1 minute retry and a 6 hour retry. This all works fine but then I found that long delay retries are not a good option.Could you please give me your experience about this?
Thank you!
If I were you, I would use Rebus' ability to defer messages to the future to implement this functionality.
You will need to track the number of failed delivery attempts manually though, by attaching and updating headers on the deferred message.
Something like this should do the trick:
public class YourHandler : IHandleMessages<MakeExternalApiCall>
{
const string DeliveryAttemptHeaderKey = "delivery-attempt";
public YourHandler(IMessageContext context, IBus bus)
{
_context = context;
_bus = bus;
}
public async Task Handle(MakeExternalApiCall message)
{
try
{
await MakeCallToExternalWebApi();
}
catch(Exception exception)
{
var deliveryAttempt = GetDeliveryAttempt();
if (deliveryAttempt > 5)
{
await _bus.Advanced.TransportMessage.Forward("error");
}
else
{
var delay = GetNextDelay(deliveryAttempt);
var headers = new Dictionary<string, string> {
{DeliveryAttemptHeaderKey, (deliveryAttempt+1).ToString()}
};
await bus.Defer(delay.Value, message, headers);
}
}
}
int GetDeliveryAttempt() => _context.Headers.TryGetValue(DeliveryAttemptHeaderKey, out var deliveryAttempt)
? deliveryAttempt
: 0;
TimeSpan GetNextDelay() => ...
}
When running in production, please remember to configure some kind of persistent subscription storage – e.g. SQL Server – otherwise, your deferred messages will be lost in the event of a restart.
You can configure it like this (after having installed the Rebus.SqlServer package):
Configure.With(...)
.(...)
.Timeouts(t => t.StoreInSqlServer(...))
.Start();
I am using MassTransit 3.0.0.0 and I have a hard time understanding how to intercept messages in a Request-Response scenario on their way out and add some information to the headers field that I can read on the receiver's end.
I was looking at the Middleware, as recommended in the MassTransit docs - see Observers warning - but the context you get on the Send is just a Pipe context that doesn't have access to the Headers field so I cannot alter it. I used the sample provided in Middleware page.
I then, looked at IPublishInterceptor
public class X<T> : IPublishInterceptor<T> where T : class, PipeContext
{
public Task PostPublish(PublishContext<T> context)
{
return new Task(() => { });
}
public Task PostSend(PublishContext<T> context, SendContext<T> sendContext)
{
return new Task(() => { });
}
public Task PrePublish(PublishContext<T> context)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
public Task PreSend(PublishContext<T> context, SendContext<T> sendContext)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
}
Which is very clear and concise. However, I don't know where it is used and how to link it to the rest of the infrastructure. As it stands, this is just an interface that is not really linked to anything.
If you need to add headers when a message is being sent, you can add middleware components to either the Send or the Publish pipeline as shown below. Note that Send filters will apply to all messages, whereas Publish filters will only apply to messages which are published.
// execute a synchronous delegate on send
cfg.ConfigureSend(x => x.Execute(context => {}));
// execute a synchronous delegate on publish
cfg.ConfigurePublish(x => x.Execute(context => {}));
The middleware can be configured on either the bus or individual receive endpoints, and those configurations are local to where it's configured.
You can also add headers in the consumer class:
public async Task Consume(ConsumeContext<MyMessage> context)
{
....
await context.Publish<MyEvent>(new { Data = data }, c => AddHeaders(c));
}
public static void AddHeaders(PublishContext context)
{
context.Headers.Set("CausationId", context.MessageId);
}
http://masstransit-project.com/MassTransit/advanced/middleware/custom.html
Shows adding an extension method to make it clear what you're setup. That's a big help if it's an interceptor that will be used a lot, so it's clear that purpose. You can skip that step if you want.
Basically, just...
cfg.AddPipeSpecification(new X<MyMessage>());
When configuring the transport.