In the following code (from https://github.com/JasonGT/NorthwindTraders/blob/master/Src/WebUI/Controllers/BaseController.cs), it's a base control inherited by all controllers.
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class BaseController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
The sub-class controllers then just use the property of Mediator.
How it differs from just adding services.AddScope<Mediator>(); in Startup.cs and then inject mediator.
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// services.AddSingleton<Mediator>();
services.AddScope<Mediator>();
The difference is that you don't need to inject IMediator to the constructor of BaseController and to all sub classes of BaseController.
So it's saves some boilerplate, but also makes the dependency less explicit.
Side note, Microsoft recommends to prefer the injection over RequestServices
The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.
Request Services represent the services configured and requested as part of the app. When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.
Generally, the app shouldn't use these properties directly. Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. This yields classes that are easier to test.
Note
Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.
See Microsoft docs
Related
I try to use SOLID, and use dependency injection design to loose couple. I splits RazorUI assembly and DbContext assembly (2 Project). So i can test whatever inside ViewModel as standalone. I curious ILogger < IndexModel > works. How to instruct compiler to know this things. As we see ILogger is interface
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger?view=dotnet-plat-ext-7.0
.
public class IndexModel : PageModel
{
private readonly IApplicationDbContext _context;
private readonly ILogger _logger;
public IndexModel(ILogger < IndexModel > logger, IApplicationDbContext context)
{
_logger = logger;
_context = context;
}
}
Is like theres something i need to setup in Program.cs to instruct cast IApplicationDbContext to ApplicationDbContext.
You need to configure your dependency injection container.
The way in which this is done varies by version. From the documentation:
ASP.NET Core's built in DI system supports constructor injection, so it resolves implementations of dependencies passed in as parameters to the constructor method of objects. Before it can do that, the implementations must be registered with the container. Typically, implementations (or "services") are registered in Program.cs from .NET 6 onwards, or the ConfigureServices method in the Startup class in earlier versions of .NET.
For .NET 6 and up (Program.cs):
builder.Services.AddTransient<IApplicationDbContext, ApplicationDbContext>();
You can read more about other versions as well as additional details such as lifetime of your service instances here.
I have a project in .NET 5 with RazorPages, I set this code to validate the Dependecy Injection in the Progam.cs file:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
})....
I forgot to register the service that is injected into my page, so I would have expected that when I try to start the app an error page would show this kind of problem, but I don't understand why it doesn't happen, because for example in case I don't register ILocalizerService this happens:
This is my RazorPage:
public class SignupModel : IdentityPageModel
{
[BindProperty]
public Models.Account.Signup Signup { get; set; }
private readonly CustomUserManager _userManager;
private readonly ILogger<SignupModel> _logger;
private readonly INcsService _ncsService;
public SignupModel(CustomUserManager userManager,
ILogger<SignupModel> logger,
INcsService ncsService) : base(localizerService)
{
Guard.Against.Null(userManager, nameof(userManager));
Guard.Against.Null(logger, nameof(logger));
Guard.Against.Null(ncsService, nameof(ncsService));
_userManager = userManager;
_logger = logger;
_ncsService = ncsService;
}
// Other code....
}
This is my service:
[PublicAPI]
public class NcsService : INcsService
{
private readonly IHttpClientFactory _httpClientFactory;
public NcsService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
// Other code...
}
I have only registered IHttpClientFactory but not INcsService interface and implementation:
services.AddHttpClient(nameof(NcsService), client =>
{
client.BaseAddress = new Uri(ncsSettings.BaseUri);
client.DefaultRequestHeaders.Add("x-functions-key", ncsSettings.ApiKey);
client.DefaultRequestHeaders.Add("x-app-name", "TSID");
}).AddHeaderPropagation(options =>
{
options.Headers.Add("x-request-id");
options.Headers.Add("x-correlation-id");
})
.AddPolicyHandler(GetRetryPolicy());
I hope I was clear.
Thank you
The root of the issue is Microsoft's default IComponentActivator implementation (the DefaultComponentActivator). The Component Activator is in control of creating your Razor Pages, but the built-in behavior does not request those pages from the built-in container. Instead, it just creates them using Activator.CreateInstance.
This means that Blazor does not register your pages in the built-in container and because of that, the page will not be part of the container's verification process.
This is, IMO, a design flaw in Blazor, because it well known, and well understood that, in case you are using a DI Container, you should let all your application components go through the container pipeline. That's the only way that the container can give you a reasonable amount of certainty about the validity of your application components.
Blazor, however, is not the only part of the ASP.NET Core framework where this happens. ASP.NET MVC Controllers, for instance, by default aren't registered in the container, and aren't resolved from the container. This is configurable though, but since this is not the default behavior, the ValidateOnBuild gives a false sense of security.
Other containers might have a more sensible default. Simple Injector, for instance, (the container that I maintain) contains extension methods that always register all MVC controllers up front. And with the Blazor integration, similar things happen.
If you stick with the built-in container, it would be good to ensure all components are resolved from the container. With MVC this is easy, because you can simply call AddControllersAsServices. With Blazor, unfortunately, this is much more difficult, because there exists no such method as AddComponentsAsServices. This means that you have to create a custom IComponentActivator that calls back into the container. But still, you'll likely have to fallback to the original behavior using Activator.CreateInstance for all Blazor Components that are created by Microsoft, because it might be much harder to find and register them using reflection. For inspiration on how to create such custom Component Activator and register your application Blazor components, take a look at the code presented here.
I have an application in asp.net core 2.1. I have registered/injected HttpContextAccessor in startup.cs file as like below.
services.AddHttpContextAccessor();
Also i have created an object for the interface IHttpContextAccessor and initiated that object in the constructor of Dependency injection class "GetScopedServicesTools"
public readonly IHttpContextAccessor HttpContextAccessor;
public GetScopedServicesTools(IHttpContextAccessor _httpContextAccessor)
{
HttpContextAccessor = _httpContextAccessor;
}
I have created an object for the DI class "GetScopedServicesTools" but am not sure what value need to be passed to the constructor of that class.
private GetScopedServicesTools getScopedServices;
Could you please help me what value need to be pass to the constructor of the class "GetScopedServicesTools".
If you want to use that class with and with the DI container, you should also register GetScopedServicesTools with the service provider.
In your Startup.cs you can do services.AddScoped<GetScopedServicesTools>(); and then simply inject it in your controllers or wherever you like.
Note:
I would like to point however that the DI mechanism is already providing out-of-the-box what you're trying to achieve with your GetScopedServicesTools class. You can simply inject the IHttpContextAccessor wherever you like since you already registered it with the service provider with services.AddHttpContextAccessor();. What you're doing with your extra class is an anti-pattern and should be avoided.
Having soured books, I can't figure out what in a C# MVC project I'm taking over causes the Controller constructors to always be passed a repository argument.
public partial class AdminController : ApiController
{
IDataRepository _repo;
public AdminController(IDataRepository repo)
{
_repo = repo;
}
}
Even other classes who are NOT partials are written this way.
I have looked at the class (or Interface) that these inherit from.
Any ideas?
Thanks in advance.
This is called dependency injection.
Asp.net core has a built-in DI container. But for old Asp.net projects, you should add a library to use this. I don't know which library you use, but I can give some common examples:
Simple Injector
https://simpleinjector.readthedocs.io/en/latest/aspnetintegration.html
// You'll need to include the following namespaces
using System.Web.Mvc;
using SimpleInjector;
using SimpleInjector.Integration.Web;
using SimpleInjector.Integration.Web.Mvc;
// This is the Application_Start event from the Global.asax file.
protected void Application_Start(object sender, EventArgs e) {
// Create the container as usual.
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
// Register your types, for instance:
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
// This is an extension method from the integration package.
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
}
Castle Windsor
https://gist.github.com/martinnormark/3128275
public class ControllersInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(AllTypes.FromThisAssembly()
.Pick().If(t => t.Name.EndsWith("Controller"))
.Configure(configurer => configurer.Named(configurer.Implementation.Name))
.LifestylePerWebRequest());
}
}
Asp.net Core
https://learn.microsoft.com/tr-tr/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<IMyDependency, MyDependency>();
}
Like others have said, dependency injection. But some more details:
.net framework and .net core handle it differently.
It's not built in at all in the .net framework. You'll want to check you global.asax file to figure out whats going on. Most of the time its done through a nuget package. there are many popular ones like autofaq, ninject, simple injector etc. You should see some stuff about building a container and registering services.
.net core has its own dependency injection framework it ships with, so quite often that's used. Although sometimes people still use a nuget package for something more complex. The .net core stuff will be in the Startup.cs file. See if theres anything like services.AddTransient anywhere.
It's possible, though unlikely someone wrote there own dependency injection framework in your project. In that case you'd be looking for them to have written a ControllerFactory implementation.
If you can't figure it out from here, please add either the global.asax file, or the startup.cs file to your question.
This is Dependency Injection, have a look in startup for something like
services.AddTransiant<IDataRepository, DataRepo>();
This tells the dependency injection container to instantiate IDataRepository with an instance of DataRepo.
I'm using ASP.NET Core and Autofac. Almost everything is registered as per lifetime scope ("per request"). So my database context DbContext is the same instance throughout a request.
However I have a singleton which also depends on DbContext. To avoid a captive dependency, it is injected as Func<Owned<DbContext>>, which means a new DbContext instance each time.
The problem is I need the same instance, as everywhere else during the request, not a new one.
I want to avoid a captive dependency bug, but I also want the same instance. Is that possible via tagging or a custom registration?
From the comments the least "architectural" painful approach may be by creating your own Scoped<T> class which will resolve the DbContext from current HttpContext
// Use an interface, so we don't have infrastructure dependencies in our domain
public interface IScoped<T> where T : class
{
T Instance { get; }
}
// Register as singleton too.
public sealed class Scoped<T> : IScoped<T> where T : class
{
private readonly IHttpContextAccessor contextAccessor;
private HttpContext HttpContext { get; } => contextAccessor.HttpContext;
public T Instance { get; } => HttpContext.RequestServices.GetService<T>();
public Scoped(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
}
}
Register it as
// Microsoft.Extensions.DependencyInjection
services.AddSingleton(typeof(IScoped<>), typeof(Scoped<>);
// Autofac
containerBuilder.RegisterType(typeof(Scoped<>))
.As(typeof(IScoped<>));
Then inject this into your validator service.
public class CustomerValidator: AbstractValidator<Customer>
{
private readonly IScoped<AppDbContext> scopedContext;
protected AppDbContext DbContext { get } => scopedContext.Instance;
public CustomValidator(IScoped<AppDbContext> scopedContext)
{
this.scopedContext = scopedContext ?? throw new ArgumentNullException(nameof(scopedContext));
// Access DbContext via this.DbContext
}
}
This way you can inject any scoped service w/o further registrations.
Additional notes
Autofac is considered a "conformer" (see docs) DI and integrates well with ASP.NET Core and Microsoft.Extensions.DependencyInjection.
From the documentation
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add services to the collection.
services.AddMvc();
// Create the container builder.
var builder = new ContainerBuilder();
// Register dependencies, populate the services from
// the collection, and build the container. If you want
// to dispose of the container at the end of the app,
// be sure to keep a reference to it as a property or field.
builder.RegisterType<MyType>().As<IMyType>();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
There a few subtle differences to the default usage of Startup class and Microsoft.Extensions.DependencyInjection container.
ConfigureServices isn't void anymore, it returns IServiceProvider. This will tell ASP.NET Core to use the returned provider instead of DefaultServiceProvider from Microsoft.Extensions.DependencyInjection.
We return the Autofac container adapter: new AutofacServiceProvider(this.ApplicationContainer) which is the root container.
This is important to make ASP.NET Core use the container everywhere in ASP.NET Core, even inside middlewares which resolve per request dependencies via HttpContext.RequestedServices.
For that reasons you can't use .InstancePerRequest() lifetime in Autofac, because Autofac isn't in control of creating scopes and only ASP.NET Core can do it. So there is no easy way to make ASP.NET Core use Autofac's own Request lifetime.
Instead ASP.NET Core will create a new scope (using IServiceScopeFactory.CreateScope()) and use a scoped container of Autofac to resolve per-request dependencies.