I've started to implement health checks in my .NET Core Web API. There are two health checks, one for checking if the SQL Server has any pending migrations and the other is checking if another API is live. Both added within ConfigureServices in Startup class.
In order to do the migration check, I need to access the DbContext which has already been added to DI using AddDbContext and to check the API, I need to get the API base url from configuration which is already in DI using services.Configure<>. I use the following code to get access to the DbContext.
I'm using AspNetCore.HealthChecks.Uris package to use AddUrlGroup health check.
var sp = services.BuildServiceProvider();
var dbContext = sp.GetService<AppDbContext>();
var apis = sp.GetService<IOptions<InternalServicesConfiguration>>().Value;
services.AddHealthChecks().AddCheck("Database", new SqlDatabaseHealthCheck(dbContext), tags: new[] { "ready" })
.AddUrlGroup(new Uri(new Uri(apis.Api1BaseUri), "/health/live"), HttpMethod.Get, "API 1", HealthStatus.UnHealthy, new []{"ready"});
But services.BuildServiceProvider() shows the following warning:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'
I can get the api base urls using
_configuration.GetSection("InternalServicesConfiguration").Get(typeof(InternalServicesConfiguration));
But I can't think of an alternative way to access the DbContext.
Any help much appreciated.
You can register your healthcheck like this:
services.AddHealthChecks()
.AddCheck<ExampleHealthCheck>("Database");
And then just inject your DbContext into ExampleHealthCheck class, which has to implement IHealthCheck interface
There are some healthchecks you can use directly for EF in the official docs
But if you want to write any custom or more complex checks, your best bet might be to create a class that implements the IHealthCheck interface, where you can inject anything you want.
Also from the docs about Custom health checks, an example:
public class ExampleHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default(CancellationToken))
{
var healthCheckResultHealthy = true;
if (healthCheckResultHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}
return Task.FromResult(
HealthCheckResult.Unhealthy("An unhealthy result."));
}
}
which, as kebek alerady answered, you will register like
services.AddHealthChecks()
.AddCheck<ExampleHealthCheck>("example_health_check");
With regard to accessing IOptions (please see updated health check). Is there another way other than getting it using _configuration.GetSection("").Get<>()
You could register those options in a following way (in ConfigureServices):
services.Configure<InternalServicesConfiguration>(Configuration.GetSection("InternalServicesConfiguration"));
And to get those options, in your class just inject IOptions<InternalServicesConfiguration> options, where the options.Value prop is the configuration value
Related
I'm working with a codebase (Minimal APIs : .NET 6) which exposes a custom middleware (UseCustomMiddleware) that is added to IApplicationBuilder via extension methods.
The second parameter of UseCustomMiddleware is a Func<HttpRequest, Identity, Message, ... Task<(bool Pass, Error Error)> that act as a predicate for providing authentication mechanism.
Here's the layout in Program.cs:
builder.Services.AddScoped<AuthenticationService>();
var app = builder.Build();
app.UseCustomMiddleware<IContract,Methods>("/", async (httpRequest, accessibility, message, ...) =>
{
//resolving dependencies here is not a problem.
var authenticationService = app.Services.CreateScope().ServiceProvider.GetRequiredService<AuthenticationService>();
//the rest of logic continues...
});
Everything works fine but the logic inside lambda is getting lengthier and lengthier and I need to move that to a separate class file.
I could create a static class and define the same static method with the signature of Func<...> and reference it in place of lambda but then I don't know how to resolve dependencies in there.
What is the proper way to achieve this?
Not sure what UseCustomMiddleware is but you don't need app.Services.CreateScope().ServiceProvider... (also you don't dispose the scope which is bad). Middleware should have access to HttpContext, which has RequestServices property which you should use to resolve services. In theory you can try to get it from HttpRequest:
app.UseCustomMiddleware<IContract,Methods>("/", async (httpRequest, accessibility, message, ...) =>
{
var authenticationService = httpRequest.HttpContext.RequestServices.GetRequiredService<AuthenticationService>();
});
Also see samples in the docs, especially for middlewares extracted into classes, I would argue they are more suitable for complex logic then ones with Func handlers.
We're using ASP.NET core and are running into the problem, that some registered services (from third-party libraries) request a specific 'service' (based on an interface) that has been deprecated.
The problem is that we don't know which libraries are using this deprecated service interface.
What we've done so far is:
create a custom implementation for the service interface
and registered this custom implementation (class) with DI
// Registration
services.AddTransient<IServiceInterface>((services) => new CustomCustomService(Log.Logger));
// Custom implementation
internal class CustomService : IServiceInterface
{
public CustomService (ILogger logger)
{
logger.Warning("!!! CustomService is still being used !!!");
}
}
So now we can see that the unwanted service is being used 'somewhere'.
But is it in some way possible to detect for which service the deprecated service has been created?
I've tried listing the stack trace using
var st = new System.Diagnostics.StackTrace();
logger.Warning("!!! CustomService is still being used !!!" + Environment.NewLine + "{stacktrace}", st.ToString());
But that doesn't seem to give information about the service using the deprecated service...
You can try the following:
var registrationsDependingOnMyService =
from descriptor in services
where descriptor.ImplementationType != null
let dependencies =
from ctor in descriptor.ImplementationType!.GetConstructors()
from param in ctor.GetParameters()
select param.ParameterType
where dependencies.Contains(typeof(IServiceInterface))
select descriptor;
This will query the IServiceCollection for registrations whose implementation type have a constructor argument of type IServiceInterface.
This might not be a bulletproof solution, as types or registrations can more sneakily depend on the service collection (e.g. by calling back into the IServiceProvider from within a registration delegate), but this is likely the best you can do with MS.DI.
So basically - as expected - it's not possible to exactly know which libraries (for which you don't have the code) use a certain dependency.
It's just trial and error ;)
Thanks for the ideas everyone.
I use repository method to get all data from DB.
Here is the code of it:
public class ExperienceRepository
{
private readonly ToSeeDatabaseContext _context;
public ExperienceRepository(ToSeeDatabaseContext context)
{
_context = context;
}
public List<Experience> GetAllExperiences()
{
return _context.Experience.ToList();
}
}
I need to call GetAllExperience from controller.
So at first I need to declare repo as private property
I do it like this
private ExperienceRepository _exprepo = new ExperienceRepository();
But it says, it need
Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required formal parameter 'context' of 'ExperienceRepository.ExperienceRepository(ToSeeDatabaseContext)' TooSeeWeb C:\Users\EugeneSukhomlyn\source\Workspaces\TooSee\Too See Web\Too See Web\Too See Web\Controllers\ExperienceController.cs 14 Active
How I can solve it?
Since you are using dependency injection, the preferred way would be to inject the DB context to the repository.
You probably have already code similar to this in the ConfigureServices method of your Startup.cs file (or in the place where you configure your service collection if you are not using ASP.NET Core) to set up the context for dependency injection (if you don't you should add it):
services.AddDbContext<ToSeeDatabaseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ToSeeDatabaseContext")));
Since your experience repository already accepts ToSeeDatabaseContext, it is already ready for dependency injection. Now you have to inform the DI framework about ExperienceRepository, so that it can inject it to its consumers. So in ConfigureServices you add:
services.AddTransient<ExperienceRepository, ExperienceRepository>();
Now can use dependency injection whenever you want to need the repository. In your consumer (eg. an ASP.NET page) you can use constructor injection to get a repository:
class MyExperienceConsumer {
private ExperienceRepository _exprepo;
public MyExperienceConsumer(ExperienceRepository exprepo) {
_exprepo = exprepo;
}
}
If your consumer is an ASP.NET page controller, this is all you need to do, since the MVC framework will create the controller for you and use DI to give you the repository. If you need to instantiate the consumer yourself you need to do so with the help a service provider from the DI framework, so that it can do its magic (assuming you have a service collection). When you use ActivatorUtilities, the DI framework will inject the repository into the constructor:
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
MyExperienceConsumer consumer =
ActivatorUtilities.CreateInstance<MyExperienceConsumer>(serviceProvider);
In any case, you can use the DI framework to do the heavy lifting for you.
Your ExperienceRepository class have one constructor that requires a ToSeeDatabaseContext as parameter.
You are trying to create a instance ExperienceRepository with no parameters. The compiler can't find a constructor which doesn't take any parameters, producing the compiler error.
I'm in a situation where the classic functionality of vnext's DI container is not enough to provide me with the correct functionality. Let's say I have a DataService that gets data from a database like this:
public class DataService : IDataService, IDisposable {
public List<MyObject> GetMyObjects()
{
// do something to fetch the data...
return myObjects;
}
}
I can then register this service in the DI container during the configuration phase in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDataService), typeof(DataService));
}
This ensures the correct lifecylce of the service (one per request scope), however, I need the service to access a different database when a different request is made. For simplicity reasons, let's say the following scenario applies:
when a request to my Web API is made, the DataService will access the currently logged in user, which contains a claim called Database which contains the information which database to use.
the DataService is then instantiated with the correct database connection.
In order to get the second step to work, I have created a constructor for the DataService like this:
public DataService(IHttpContextAccessor accessor)
{
// get the information from HttpContext
var currentUser = accessor.HttpContext.User;
var databaseClaim = currentUser.Claims.SingleOrDefault(c => c.Type.Equals("Database"));
if (databaseClaim != null)
{
var databaseId = databaseClaim.Value;
// and use this information to create the correct database connection
this.database = new Database(databaseId);
}
}
By using the currently logged in user and his claims, I can ensure that my own authentication middleware takes care of providing the necessary information to prevent attackers from trying to access the wrong database.
Of course adding the IDisposable implementation is required to cleanup any database connections (and gets called correctly using the scope lifecycle).
I can then inject the DataService into a controller like this
public MyController : Controller
{
private IDataService dataService;
public MyController(IDataService dataService)
{
this.dataService = dataService;
}
}
This all works fine so far.
My questions now are:
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Is this method really safe? Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
What you have is fine.
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Not really. You can use delegate registration but it's the same problem.
Is this method really safe?
Yes
Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
Nope. The IHttpContextAcessor uses AsyncLocal (http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html) to provide access to the "current" http context.
I am trying to set up my AutoFac registration in such a way that this test passes:
[Test]
public void Autofac_registration_test()
{
// Given
var builder = new ContainerBuilder();
RegisterServices(builder);
var container = builder.Build();
// When
var firstHub = container.Resolve<Hub>();
var secondHub = container.Resolve<Hub>();
// Then
firstHub.Should().NotBe(secondHub);
firstHub.FooRepo.Context.Should().Be(firstHub.BarRepo.Context);
firstHub.FooRepo.Context.Should().NotBe(secondHub.FooRepo.Context);
}
i.e. I want to use the same Context object all the way down within a single Hub, but use a different one when a new Hub is created.
RegisterServices is currently just:
private void RegisterServices(ContainerBuilder builder)
{
builder.RegisterType<MyHub>();
builder.RegisterType<FooRepo>();
builder.RegisterType<BarRepo>();
builder.RegisterType<Context>(); // How should I scope this?
}
Which fails at firstHub.FooRepo.Context.Should().Be(firstHub.BarRepo.Context); because Context is transiently scoped.
But scoping context per lifetime also fails, this time at firstHub.FooRepo.Context.Should().NotBe(secondHub.FooRepo.Context);.
It feels like this is a reasonable thing to want to do, so am I missing anything obvious out-of-the-box here?
Or will I have to do something manual to track Hub creation?
(For context, this is for a SignalR app. Hubs are created per SignalR request, so this was an attempt to match the unit-of-work lifetime of an HTTP request in normal webby situations).
What #Steven said in his comment was correct, I needed a per-object-graph lifestyle.
Castle.Windsor supports this, so I swicthed to using that for my dependency injection instead of AutoFac. The registration now looks like:
container.Register(Component.For<Hub>().LifestyleTransient());
container.Register(Component.For<FooRepo>().LifestyleTransient());
container.Register(Component.For<BarRepo>().LifestyleTransient());
container.Register(Component.For<Context>().LifestyleBoundTo<Hub>()); // Important bit
For more information, see: http://docs.castleproject.org/Windsor.LifeStyles.ashx?HL=scope#Bound_8