Tracing MediatR using Application Insights in ASP.NET Core - asp.net-core

What is the currently recommended way to trace MediatR using Application Insights in an ASP.NET Core Application?
Our goal is to drill down the individual pipelines in an application from Request, Mediator Call to Response.
Way 1: Wrap Mediator in an action
[HttpGet]
[Route("", Name = MachinesRoutes.Names.GetMachines)]
public async Task<IActionResult> GetMachines()
{
MachinesProjection p;
using (IOperationHolder<RequestTelemetry> operation = _telemetryClient.StartOperation<RequestTelemetry>(nameof(GetMachinesCommand)))
{
p = await _mediator.Send(new GetMachinesCommand());
}
// ....
}
Way 2: own dispatcher
public class MediatorDispatcher : IEventDispatcher
{
private readonly IMediator _mediator;
private readonly TelemetryClient _telemetryClient;
public MediatorDispatcher(IMediator mediator, TelemetryClient telemetryClient )
{
_mediator = mediator;
_telemetryClient = telemetryClient;
}
public async Task<TResponse> Send<TResponse>(IRequest<TResponse> command, CancellationToken cancellationToken = default)
{
using IOperationHolder<RequestTelemetry> operation = _telemetryClient.StartOperation<RequestTelemetry>(command.GetType().FullName);
return await _mediator.Send(command, cancellationToken).ConfigureAwait(false);
}
Way 3 ...?
In the end, we want to have a way to recognize requests that take an unusually long time.
It should also be possible to recognize what caused the request duration, i.e. a drill down of the operation.
Thanks.

I Agree with #Peter Bons . Behavior would be the most optimum way in your scenario.
behaviors, which allow you to build your own pipeline directly inside of MediatR without resolving to using decorators around your handlers. It's a more natural way to enhance your handlers with behavior and better supported in containers.
A pipeline behavior is an implementation of IPipelineBehavior. It represents a similar pattern to filters in ASP.NET MVC/Web API or pipeline behaviors in NServiceBus. Your pipeline behavior needs to implement one method:
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next);
Two built-in behaviors exist:
RequestPreProcessorBehavior will execute IRequestPreProcessor implementations before any handlers are called
RequestPostProcessorBehavior will execute IRequestPostProcessor implementations after all handlers are called
You can find these in the MediatR.Pipeline namespace, and will need to be explicitly registered with your container as open generics.
Additionally, any IRequestPreProcessor and IRequestPostProcessor will need to be registered with the container.
You can find the sample pipeline here.
Hope it helps.

Related

I get "A second operation was started on this context before a previous operation completed" error just in one request

In my netcoreapp3.1 project I got the error just from one request I've recently added to my project: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext.
I couldn't find solution because I wrote await before every async request and my db context is transient.
My Db Context:
services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
},
ServiceLifetime.Transient
);
And this is where I got the error:
public class ProductDataRepository : UpdatableRepository<PRODUCT_DATA>, IProductDataDAL
{
private readonly MyContext _context;
public ProductDataRepository(MyContext context) : base(context)
{
_context = context;
}
public async Task<PRODUCT_DATA> GetProductById(string productId)
{
return await _context.PRODUCT_DATA.AsNoTracking().FirstOrDefaultAsync(pd => pd.PRODUCTID == productId);
}
public async Task<bool> IsProductMeltable(string productId)
{
// here is where I got the error
return await _context.MELTABLE_PRODUCTS.AsNoTracking().AnyAsync(x => x.PRODUCTID.Equals(productId));
}
}
And my DI:
services.AddScoped<IProductDataDAL, ProductDataRepository>();
services.AddScoped<IProductDataService, ProductDataManager>();
In manager:
public Task<bool> IsProductMeltable(string productId)
{
return await _productDataDAL.IsProductMeltable(productId);
}
In controller:
myModel.IS_MELTABLE = await _commonService.ProductDataService.IsProductMeltable(productData.PRODUCTID);
I also changed my methods from async to sync but still got the same error.
Thanks for your help in advance
Without seeing all the places that these methods might be called, it is difficult to find the source.
But, two things that may help:
The error reported does indicate that the same context is being called multiple times.
Transient means that the DI container will provide a brand new instance each time one is requested.
Regarding that second point, be aware that you are injecting it into a 'scoped' service.
So, this means that whilst your context is 'transient' that does not mean a brand new context is provided each time it is called. It means that a new one is requested each time a context is requested.
As your other services are 'scoped' this means that they only request a context once per request scope. So, even though your context is registered as transient, the SAME context will be used throughout the lifetime of a scoped service that requests it.
I notice that your calling your repository from different layers. One from controller and one from a manager service. This is likely to cause challenges.
Try to keep each layer having different responsibilities.
Best to use the controller as a very thin layer to simply receive HttpRequests and immediately pass responsibilty over to a service layer to do business logic and interact with repositories.
Cleaning that up a little may help you identify the problem.

InputFormatter for Single Request

Is there a way to include an InputFormatter which only runs for a single endpoint?
We have 1 solitary endpoint which has a need for a custom InputFormatter.
So we don't really want to add an input formatter globally, for the benefit of a single endpoint. I don't really want to write a hacky middleware which would run for every request either. Some kind of ActionFilter would have been perfect.
I've seen existing SO answers on this very topic, but they all have answers which require an outdated API e.g. the InputFormatters collection is no longer available on the context in Action Filters.
Cheers
Here is an example which helps you to control the input formatter for an action method.
public class CSPContentTypeFormatterAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var options = context
.HttpContext
.RequestServices
.GetService(serviceType: typeof(IOptions<MvcOptions>)) as IOptions<MvcOptions>;
var mvcOptions = options.Value;
mvcOptions.InputFormatters.OfType<SystemTextJsonInputFormatter>().First()
.SupportedMediaTypes.Add(
new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/csp-report")
);
base.OnResultExecuting(context);
}
}

How best to handle data fetching needed for FluentValidation

In the app I'm working on, I'm using Mediatr and its pipelines to handle database interaction, some minor business logic, validation, etc.
There's a few checks for things like access control I can handle in the pipeline, since I'm using a context object as described here https://jimmybogard.com/sharing-context-in-mediatr-pipelines/ to go from ASP.Net identity to a custom context object with user information and claims.
One problem I'm having is that since this application is multi-tenant, I need to ensure that even if an object exists, it belongs to that tenant, and the only way to be sure of that is to grab the object from the database and check it. It seems to me the validation shouldn't have side effects, so I don't want to rely on that to populate the context object. But then that pushes a bunch of validation down into the Mediatr handlers as they check for object existence, and so on, leading to a lot of repeated code. I don't really want to query the database multiple times since some queries can be expensive.
Another issue with doing the more complicated validation in the actual request handlers is getting what are essentially validation errors back out. Currently, if one of these checks fail I throw a ValidationException, which is then caught by middleware and turned into a ProblemDetails that's returned to the API caller. This is basically exceptions as flow control, and a validation failure really isn't "exceptional" anyhow.
The thoughts I'm having on how to solve this are:
Somewhere in the pipeline, when I'm building the context, include attempting to fetch the objects needed from the database. Validation then fails if any of these are null. This seems like it would make testing harder, as well as needing to decorate the requests somehow (or use reflection) so the pipeline can know to attempt to load these objects.
Have the queries in the validator, but use some sort of cache aware repository so when the same object is queried later, it's served from the cache, and not the database. The handlers would also use this cache aware repository (Currently the handlers interact directly with the EF Core DbContext to query). This then adds the issue of cache invalidation, which I'm going to have to handle at some point, anyhow (quite a few items are seldom modified). For testing, a dummy cache object can be injected that doesn't actually cache anything.
Make all the responses from requests implement an interface (or extend an abstract class) that has validation info, general success flags, etc. This can either be returned through the API directly, or have some pipeline that transforms failures into ProblemDetails. This would add some boilerplate to every response and handler, but avoids exceptions as flow control, and the caching/reflection issues in the other options.
Assume for 1 and 2 that any sort of race conditions are not an issue. Objects don't change owners, and things are seldom actually deleted from the database for auditing/accounting purposes.
I know there's no true one size fits all for problems like this, but I would like to know if there's additional options I'm missing, or any long term maintainability issues anyone with a similar pipeline has encountered if they went with one of these listed options.
We use MediatR IRequestPreProcessor for fetching data that we need both in RequestHandler and in FluentValidation validators.
RequestPreProcessor:
public interface IProductByIdBinder
{
int ProductId { get; }
ProductEntity Product { set; }
}
public class ProductByIdBinder<T> : IRequestPreProcessor<T> where T : IProductByIdBinder
{
private readonly IRepositoryReadAsync<ProductEntity> productRepository;
public ProductByIdBinder(IRepositoryReadAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public async Task Process(T request, CancellationToken cancellationToken)
{
request.Product = await productRepository.GetAsync(request.ProductId);
}
}
RequestHandler:
public class ProductDeleteCommand : IRequest, IProductByIdBinder
{
public ProductDeleteCommand(int id)
{
ProductId = id;
}
public int ProductId { get; }
public ProductEntity Product { get; set; }
private class ProductDeleteCommandHandler : IRequestHandler<ProductDeleteCommand>
{
private readonly IRepositoryAsync<ProductEntity> productRepository;
public ProductDeleteCommandHandler(
IRepositoryAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public Task<Unit> Handle(ProductDeleteCommand request, CancellationToken cancellationToken)
{
productRepository.Delete(request.Product);
return Unit.Task;
}
}
}
FluentValidation validator:
public class ProductDeleteCommandValidator : AbstractValidator<ProductDeleteCommand>
{
public ProductDeleteCommandValidator()
{
RuleFor(cmd => cmd)
.Must(cmd => cmd.Product != null)
.WithMessage(cmd => $"The product with id {cmd.ProductId} doesn't exist.");
}
}
I see nothing wrong with handling business logic validation in the handler layer.
Moreover, I do not think it is right to throw exceptions for them, as you said it is exceptions as flow control.
Introducing a cache seems like overkill for the use case too. The most reasonable option is the third IMHO.
Instead of implementing an interface you can use the nifty OneOf library and have something like
using HandlerResponse = OneOf<Success, NotFound, ValidationResponse>;
public class MediatorHandler : IRequestHandler<Command, HandlerResponse>
{
public async Task<HandlerResponse> Handle(
Command command,
CancellationToken cancellationToken)
{
Resource resource = await _userRepository
.GetResource(command.Id);
if (resource is null)
return new NotFound();
if (!resource.IsValid)
return new ValidationResponse(new ProblemDetails());
return new Success();
}
And then map it in your API Layer like
public async Task<IActionResult> PostAsync([FromBody] DummyRequest request)
{
HandlerResponse response = await _mediator.Send(
new Command(request.Id));
return response.Match<IActionResult>(
success => Created(),
notFound => NotFound(),
failed => new UnprocessableEntityResult(failed.ProblemDetails))
);
}

How do I execute a new job on success or failure of Hangfire job?

I'm working on a Web API RESTful service that on a request needs to perform a task. We're using Hangfire to execute that task as a job, and on failure, will attempt to retry the job up to 10 times.
If the job eventually succeeds I want to run an additional job (to send an event to another service). If the job fails even after all of the retry attempts, I want to run a different additional job (to send a failure event to another service).
However, I can't figure out how to do this. I've created the following JobFilterAttribute:
public class HandleEventsAttribute : JobFilterAttribute, IElectStateFilter
{
public IBackgroundJobClient BackgroundJobClient { get; set; }
public void OnStateElection(ElectStateContext context)
{
var failedState = context.CandidateState as FailedState;
if (failedState != null)
{
BackgroundJobClient.Enqueue<MyJobClass>(x => x.RunJob());
}
}
}
The one problem I'm having is injecting the IBackgroundJobClient into this attribute. I can't pass it as a property to the attribute (I get a "Cannot access non-static field 'backgroundJobClient' in static context" error). We're using autofac for dependency injection, and I tried figuring out how to use property injection, but I'm at a loss. All of this leads me to believe I may be on the wrong track.
I'd think it would be a fairly common pattern to run some additional cleanup code if a Hangfire job fails. How do most people do this?
Thanks for the help. Let me know if there's any additional details I can provide.
Hangfire can build an execution chains. If you want to schedule next job after first one succeed, you need to use ContinueWith(string parentId, Expression<Action> methodCall, JobContinuationOptions options); with the JobContinuationOptions.OnlyOnSucceededState to run it only after success.
But you can create a HangFire extension like JobExecutor and run tasks inside it to get more possibilities.
Something like that:
public static JobResult<T> Enqueue<T>(Expression<Action> a, string name)
{
var exprInfo = GetExpressionInfo(a);
Guid jGuid = Guid.NewGuid();
var jobId = BackgroundJob.Enqueue(() => JobExecutor.Execute(jGuid, exprInfo.Method.DeclaringType.AssemblyQualifiedName, exprInfo.Method.Name, exprInfo.Parameters, exprInfo.ParameterTypes));
JobResult<T> result = new JobResult<T>(jobId, name, jGuid, 0, default(T));
JobRepository.WriteJobState(new JobResult<T>(jobId, name, jGuid, 0, default(T)));
return result;
}
More detailed information you can find here: https://indexoutofrange.com/Don%27t-do-it-now!-Part-5.-Hangfire-job-continuation,-ContinueWith/
I haven't been able to verify this will work, but BackgroundJobClient has no static methods, so you would need a reference to an instance of it.
When I enqueue tasks, I use the static Hangfire.BackgroundJob.Enqueue which should work without a reference to the JobClient instance.
Steve

Is it possible to return a response from a Web API constructor?

I have a Web API ApiController base class and I would like to perform some validations in the constructor. This might include checking the current load on the server. If it's high, I'd like to return an appropriate HttpResponseMessage indicating the requestor should try again later.
Is something like this possible?
I Haven't tested it but that's not what the constructor is for. I don't think all plumbing is set at that time.
You could use global filters for this purpose. Here you have a sample that sets a global filter for authorization, you should use a similar logic but creating your own filter for your specific purposes.
A global filter would intercept all your requests and is executed before the controller actions so is a good place to perform your task.
Even though what you are doing sounds like it may be better to revise the approach. Note that you can throw HttpResponseException since the WebApi is Rest Service HttpResponseException is the recommended way to throw Exceptions back to the client.
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("No idea what happened "),
ReasonPhrase = "Something was not Not Found"
}
throw new HttpResponseException(resp);
As long as you're using .NET 4.5, then you'd be better off creating a custom MessageHandler. You'll need to extend DelegatingHandler in order to do that.
public class MyHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(
HttpMessageRequest request, CancellationToken cancellationToken) {
// Access the request object, and do your checking in here for things
// that might cause you to want to return a status before getting to your
// Action method.
// For example...
return request.CreateResponse(HttpStatusCode.Forbidden);
}
}
Then inside your WebApiConfig, just add the following code to use the new Handler:
config.MessageHandlers.Add(new MyHandler());
You can't throw HttpResponseException in constructor, that will always cause 500.
Easiest way is to override ExecuteAsync():
public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) {
if (!myAuthLogicCheck()) {
// Return 401 not authorized
var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "User not logged in" };
throw new HttpResponseException(msg);
}
return base.ExecuteAsync(controllerContext, cancellationToken);
}