Can I eliminate this linkage in UseExceptionHandler DI - .net-core

I inject a custom error logging and reporting service into my services collection during Startup.ConfigureServices.
services.AddTransient<IErrorService, ErrorService>();
I then add a global error trap in Startup.Configure.
app.UseExceptionHandler("/Home/Error");
Then in Home.Error, I have my DI for ErrorService, and I pass it my context, and all works perfectly.
Is there some way to eliminate the linkage to Home.Error, and just invoke my ErrorService right in Configure? But I can't figure out how to get linkage to ErrorService at this point in Startup.
Note, two of the other services I inject are my EmailService and my ProgramSettings. I need both of these in ErrorService, so I do need ErrorService invoked in such a way that standard .NET Core DI is wired up.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(a => {
a.Run(ctx => {
var exceptionFeature = ctx.Features.Get<IExceptionHandlerPathFeature>();
... execute ErrorService here somehow????????
ctx.Response.StatusCode = StatusCodes.Status500InternalServerError;
return Task.CompletedTask;
});
});

Related

Why doesn't middleware execute after mapping controllers?

I am building the most basic of an ASP.NET Core Web API, and after a lot of experimenting on my own, I found that running any middleware (custom, app.run, app.use) after the .MapControllers() method doesn't work at all.
internal class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoint =>
{
endpoint.MapControllers();
});
}
}
I'm asking here because the documentation didn't clarify: how does the MapControllers() method affect other middleware in the pipeline?
I found that running any middleware (custom, app.run, app.use) after
the .MapControllers() method doesn't work at all.
The issue might relate the middleware order, using the MapController method, it already find the endpoint and return the response, so it will not invoke the next middleware.
You can refer the following code: in this sample, by using the MapControllser method, it can't match the /map1, so we can access it using https://localhost:44379/map1 (change the port to yours).
The result like this:
More detail information, see ASP.NET Core Middleware.

No service for type 'CommanderContext' has been registered

I am using .NET 5.0 and HotChoclate framework to create a GraphQL API.
Below is my ConfigureServices Method
public void ConfigureServices(IServiceCollection services)
{
services.AddPooledDbContextFactory<CommanderContext>(options =>
{
options.UseSqlServer(_configuration.GetConnectionString("default"));
});
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddFiltering()
.AddSorting()
.AddProjections();
}
To resolve the concurrency issues. I am using AddPooledDbContextFactory() method.
https://chillicream.com/docs/hotchocolate/integrations/entity-framework.
Below is my Configure Method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
InitializeDatabase(app);
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
}
private void InitializeDatabase(IApplicationBuilder app)
{
using var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope();
scope.ServiceProvider.GetRequiredService<CommanderContext>().Database.Migrate(); //Here I get the exception.
}
Since the application is in development, I want the migrations to be automatically applied to the database.
But when I try and get instance of my context class in InitializeDatabase() method, I get the above exception.
Now I have a little understanding that instead of getting a single instance my context class I am getting back a pool of context class.
My Question is: How to I automatically apply the migrations to my database.
Any help is appreciated.
Thanks
AddPooledDbContextFactory method registers IDbContextFactory<TContext> instead the DbContext itself, that's why you can't use GetRequiredService<TContext>. Instead, you should retrieve the factory, use it to obtain TContext instance, do your work and then Dispose it (which in fact will return it to the pool). You can't rely on DI to do that for you because it is not resolved (thus not maintained) by the DI system, but from another abstraction (called factory pattern).
Something like this:
using var context = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<CommanderContext>>()
.CreateDbContext();
context.Database.Migrate();

.NET Core health check: Unable to find the required services

I am trying to register a health in a sample .NET Core application check using the code in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Adding healthcheck in ConfigureServices
services.AddHealthChecks(checks =>
{
// Custom health check which implements IHealthCheck
// Breakpoint hits here and this code is executed
checks.AddCheck<CustomHealthCheck>("Storage", new TimeSpan(0, 1, 0));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
// This throws the exception: "Unable to find the required services..."
endpoints.MapHealthChecks("/health");
});
// I've tried this too. This also throws an exception
app.UseHealthChecks("/health");
}
However, when I run the application, I get the exception: "Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddHealthChecks' inside the call to 'ConfigureServices(...)' in the application startup code."
I'm finding this exception message confusing because I am calling AddHealthChecks in the ConfigureServices method. So I'm not sure if I've made a mistake, or if there's something else I need to do.
Any advice appreciated.
I've just revisited this. The issue is that the first uses the nuget package Microsoft.AspNetCore.HeatlhChecks. It should use the nuget package Microsoft.AspNetCore.Diagnostics.HealthChecks.
Confusingly, these two packages have very similar names and both define the interface IHealthCheck, so the above code doesn't generate a compiler error message. It just throws an error.

Hangfire recurring job at a specific time ASP MVC

Ive been recently using hangfire to process lengthy jobs enabling me to return API calls more efficiently within an ASP MVC Core application.
i have implimented this by adding the following to startup.cs
public class Startup
{
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()));
services.AddHangfire(configuration => configuration.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("AllowAll");
app.UseMvc();
app.UseHangfireDashboard();
app.UseHangfireServer();
}
and then calling this within an action on a controller
using (new BackgroundJobServer())
{
formData.DateCreated = DateTime.Now;
formData.Source = "Web";
formData.StatusItem = _context.Status.FirstOrDefault(x => x.Default == true);
_context.Lead.Add(formData);
_context.SaveChanges();
}
Now i have a need to send a an email at 1am every day with the status of records from the database.
Im slightly confused on the implementation from the following perspective:
-Where do i implement the background job?
-Do i put the call in a method, if so how is this called/
-I cannot find BackgroundJob.AddOrUpdate, which i understand is used for the recurring tasks
-The schedule method takes a timespan object, but all examples are using CRON.
I have all the code to create the email, i just need help scheduling it
Thanks for your help in advance.

ASP.NET Core Dependency Injection inside Startup.Configure

I am using the Cookie Middleware to authenticate the user. I have been following this official tutorial.
Inside my Startup class, an excerpt from my Configure method looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ...
// Cookie-based Authentication
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CustomCookieAuthenticationEvents(app),
});
// ...
}
The CustomCookieAuthenticationEvents class is defined as follows:
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
private IApplicationBuilder _app;
private IMyService _myService = null;
private IMyService MyService
{
get
{
if(_myService != null)
{
return _myService;
} else
{
return _myService = (IMyService) _app.ApplicationServices.GetService(typeof(IMyService));
}
}
}
public CustomCookieAuthenticationEvents(IApplicationBuilder app)
{
_app = app;
}
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
LogonSession response = null;
var response = await MyService.CheckSession(sessionToken);
if (response == null)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
Since the dependency injection is not available at Startup.Configure (the services are not even registered at that point), I made a bit of a workaround:
Pass IApplicationBuilder service to the CustomCookieAuthenticationEvents class
Fetch IMyService upon first request inside a read-only property (singleton pattern)
tl;dr
My solution works, but it's ugly. There is no dependency injection involved, as it is not possible at that time.
The essence of the problem is that I must instantiate CustomCookieAuthenticationEvents. As far as I have read the source code, there is no way around this, because the UseCookieAuthentication throws an exception if I omit the options parameter.
Any suggestion how can one make my current solution nicer?
Startup.ConfigureServices() is called before Startup.Configure() (see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup for more information). So Dependency Injection is available at that time ;)
As a consequence, you can resolve your dependence in your configure method like this:
app.ApplicationServices.GetRequiredService<CustomCookieAuthenticationEvents>()
You should be really careful when you resolve services inside middleware. Your current approach (and the one suggested by #arnaudauroux) can result in difficulties when you use/need/require scoped services (i.e. usage of DbContext).
Resolving via app.ApplicationServices results in static (singleton) services, when the service is registered as scoped (transient are resolved per call, so they are not affected). It would be better to resolve your service during the request from HttpContext inside ValidatePrincipal method.
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
LogonSession response = null;
var myService = context.HttpContext.RequestServices.GetService<IMyService >();
var response = await myService.CheckSession(sessionToken);
if (response == null)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
With this approach you don't need to pass any dependencies inside your CustomCookieAuthenticationEvents class at all. HttpContext.RequiredServices is made specifically for such classes (any other can be solved via constructor injection, but not middleware and http context related pipeline, as there is no other otherway to correctly resolve scoped services in middlewares - Middleware instance is static and only instantiated once per request)
This way you won't have lifetime issues with your scoped services.
When you resolve transient services, they will be disposed at the end of request. Whereas transient services resolved via app.ApplicationServices will be resolved at some point in future after the request is finished and when garbage collection triggers (means: your resources will be freed at the earliest possible moment, which is when the request ends).

Resources