I created an .NET Core MVC application and use Dependency Injection and Repository Pattern to inject a repository to my controller. However, I am getting an error:
InvalidOperationException: Unable to resolve service for type 'WebApplication1.Data.BloggerRepository' while attempting to activate 'WebApplication1.Controllers.BlogController'.
Repository:
public interface IBloggerRepository { ... }
public class BloggerRepository : IBloggerRepository { ... }
Controller:
public class BlogController : Controller
{
private readonly IBloggerRepository _repository;
public BlogController(BloggerRepository repository)
{
_repository = repository;
}
public IActionResult Index() { ... }
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IBloggerRepository, BloggerRepository>();
}
I'm not sure what I'm doing wrong. Any ideas?
To break down the error message:
Unable to resolve service for type 'WebApplication1.Data.BloggerRepository' while attempting to activate 'WebApplication1.Controllers.BlogController'.
That is saying that your application is trying to create an instance of BlogController but it doesn't know how to create an instance of BloggerRepository to pass into the constructor.
Now look at your startup:
services.AddScoped<IBloggerRepository, BloggerRepository>();
That is saying whenever a IBloggerRepository is required, create a BloggerRepository and pass that in.
However, your controller class is asking for the concrete class BloggerRepository and the dependency injection container doesn't know what to do when asked for that directly.
I'm guessing you just made a typo, but a fairly common one. So the simple fix is to change your controller to accept something that the DI container does know how to process, in this case, the interface:
public BlogController(IBloggerRepository repository)
// ^
// Add this!
{
_repository = repository;
}
Note that some objects have their own custom ways to be registered, this is more common when you use external Nuget packages, so it pays to read the documentation for them. For example if you got a message saying:
Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' ...
Then you would fix that using the custom extension method provided by that library which would be:
services.AddHttpContextAccessor();
For other packages - always read the docs.
I ran into this issue because in the dependency injection setup I was missing a dependency of a repository that is a dependency of a controller:
services.AddScoped<IDependencyOne, DependencyOne>(); <-- I was missing this line!
services.AddScoped<IDependencyTwoThatIsDependentOnDependencyOne, DependencyTwoThatIsDependentOnDependencyOne>();
In my case I was trying to do dependency injection for an object which required constructor arguments. In this case, during Startup I just provided the arguments from the configuration file, for example:
var config = Configuration.GetSection("subservice").Get<SubServiceConfig>();
services.AddScoped<ISubService>(provider => new SubService(config.value1, config.value2));
I was having a different problem, and yeah the parameterized constructor for my controller was already added with the correct interface. What I did was something straightforward. I just go to my startup.cs file, where I could see a call to register method.
public void ConfigureServices(IServiceCollection services)
{
services.Register();
}
In my case, this Register method was in a separate class Injector. So I had to add my newly introduced Interfaces there.
public static class Injector
{
public static void Register(this IServiceCollection services)
{
services.AddTransient<IUserService, UserService>();
services.AddTransient<IUserDataService, UserDataService>();
}
}
If you see, the parameter to this function is this IServiceCollection
Hope this helps.
Only if anyone have the same situation like me, I am doing a tutorial of EntityFramework with existing database, but when the new database context is created on the models folders, we need to update the context in the startup, but not only in services.AddDbContext but AddIdentity too if you have users authentication
services.AddDbContext<NewDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<NewDBContext>()
.AddDefaultTokenProviders();
You need to add a new service for DBcontext in the startup
Default
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
Add this
services.AddDbContext<NewDBContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("NewConnection")));
Public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IEventRepository, EventRepository>();
}
You forgot to add "services.AddScoped" in startup ConfigureServices method.
In my case, .Net Core 3.0 API in
Startup.cs,
in method
public void ConfigureServices(IServiceCollection services)
I had to add
services.AddScoped<IStateService, StateService>();
I got this issue because of a rather silly mistake. I had forgotten to hook my service configuration procedure to discover controllers automatically in the ASP.NET Core application.
Adding this method solved it:
// Add framework services.
services.AddMvc()
.AddControllersAsServices(); // <---- Super important
I had to add this line in the ConfigureServices in order to work.
services.AddSingleton<IOrderService, OrderService>();
I was getting below exception
System.InvalidOperationException: Unable to resolve service for type 'System.Func`1[IBlogContext]'
while attempting to activate 'BlogContextFactory'.\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, ISet`1 callSiteChain)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType, ServiceProvider serviceProvider)\r\n at System.Collections.Concurrent.ConcurrentDictionaryExtensions.GetOrAdd[TKey, TValue, TArg] (ConcurrentDictionary`2 dictionary, TKey key, Func`3 valueFactory, TArg arg)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)\r\n at Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)\r\n at lambda_method(Closure , IServiceProvider , Object[] )\r\n at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
Because I wanted register Factory to create instances of DbContext Derived class IBlogContextFactory and use Create method to instantiate instance of Blog Context so that I can use below pattern along with dependency Injection and can also use mocking for unit testing.
the pattern I wanted to use is
public async Task<List<Blog>> GetBlogsAsync()
{
using (var context = new BloggingContext())
{
return await context.Blogs.ToListAsync();
}
}
But Instead of new BloggingContext() I want to Inject factory via constructor as in below BlogController class
[Route("blogs/api/v1")]
public class BlogController : ControllerBase
{
IBloggingContextFactory _bloggingContextFactory;
public BlogController(IBloggingContextFactory bloggingContextFactory)
{
_bloggingContextFactory = bloggingContextFactory;
}
[HttpGet("blog/{id}")]
public async Task<Blog> Get(int id)
{
//validation goes here
Blog blog = null;
// Instantiage context only if needed and dispose immediately
using (IBloggingContext context = _bloggingContextFactory.CreateContext())
{
blog = await context.Blogs.FindAsync(id);
}
//Do further processing without need of context.
return blog;
}
}
here is my service registration code
services
.AddDbContext<BloggingContext>()
.AddTransient<IBloggingContext, BloggingContext>()
.AddTransient<IBloggingContextFactory, BloggingContextFactory>();
and below are my models and factory classes
public interface IBloggingContext : IDisposable
{
DbSet<Blog> Blogs { get; set; }
DbSet<Post> Posts { get; set; }
}
public class BloggingContext : DbContext, IBloggingContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("blogging.db");
//optionsBuilder.UseSqlite("Data Source=blogging.db");
}
}
public interface IBloggingContextFactory
{
IBloggingContext CreateContext();
}
public class BloggingContextFactory : IBloggingContextFactory
{
private Func<IBloggingContext> _contextCreator;
public BloggingContextFactory(Func<IBloggingContext> contextCreator)// This is fine with .net and unity, this is treated as factory function, but creating problem in .netcore service provider
{
_contextCreator = contextCreator;
}
public IBloggingContext CreateContext()
{
return _contextCreator();
}
}
public class Blog
{
public Blog()
{
CreatedAt = DateTime.Now;
}
public Blog(int id, string url, string deletedBy) : this()
{
BlogId = id;
Url = url;
DeletedBy = deletedBy;
if (!string.IsNullOrWhiteSpace(deletedBy))
{
DeletedAt = DateTime.Now;
}
}
public int BlogId { get; set; }
public string Url { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? DeletedAt { get; set; }
public string DeletedBy { get; set; }
public ICollection<Post> Posts { get; set; }
public override string ToString()
{
return $"id:{BlogId} , Url:{Url} , CreatedAt : {CreatedAt}, DeletedBy : {DeletedBy}, DeletedAt: {DeletedAt}";
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
----- To Fix this in .net Core MVC project -- I did below changes on dependency registration
services
.AddDbContext<BloggingContext>()
.AddTransient<IBloggingContext, BloggingContext>()
.AddTransient<IBloggingContextFactory, BloggingContextFactory>(
sp => new BloggingContextFactory( () => sp.GetService<IBloggingContext>())
);
In short in .net core developer is responsible to inject factory function, which in case of Unity and .Net Framework was taken care of.
For me it worked to add the DB context in the ConfigureServices as follows:
services.AddDBContext<DBContextVariable>();
This issue is because you didn't register the data access component with the interface written for it. Try using as follows
services.AddTransient<IMyDataProvider, MyDataAccess>();`
If you are using AutoFac and getting this error, you should add an "As" statement to specify the service that the concrete implementation implements.
Ie. you should write:
containerBuilder.RegisterType<DataService>().As<DataService>();
instead of
containerBuilder.RegisterType<DataService>();
You might be missing this:
services.AddScoped<IDependencyTwoThatIsDependentOnDependencyOne, DependencyTwoThatIsDependentOnDependencyOne>();
I received this error message with ILogger being injected into a .NET 5 class. I needed to add the class type to fix it.
ILogger logger --> ILogger <MyClass> logger
ohh, Thank #kimbaudi, i followed this tuts
https://dotnettutorials.net/lesson/generic-repository-pattern-csharp-mvc/
and got the same error as your. But after read your code i found out my solution was adding
services.AddScoped(IGenericRepository, GenericRepository);
into ConfigureServices method in StartUp.cs file =))
I had the same issue and found out that my code was using the injection before it was initialized.
services.AddControllers(); // Will cause a problem if you use your IBloggerRepository in there since it's defined after this line.
services.AddScoped<IBloggerRepository, BloggerRepository>();
I know it has nothing to do with the question, but since I was sent to this page, I figure out it my be useful to someone else.
Resolving a service is done even before the class code is reached, so we need to check our dependency injections.
In my case I added
services.AddScoped<IMeasurementService, MeasurementService>();
in StartupExtensions.cs
Had the same issue all I did was to register my DBContext in Startup.cs.
The problem is that you are calling a DBContext that the application has not registered with so it does not know what to do when your view tries to reference it.
Key part of the error message, "while attempting to activate"
private readonly SmartPayDBContext _context;
Solution that worked for me
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<SmartPayDBContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
}
Not sure if this will help anyone else, but I was correctly dependency injecting and got this error when trying to access my API controllers.
I had to shut down the project and rebuild after already adding them to my startup.cs class - for some reason a rebuild got Visual Studio to recognize the service class was properly registered when before it was getting an error.
Add services.AddSingleton(); in your ConfigureServices method of Startup.cs file of your project.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
// To register interface with its concrite type
services.AddSingleton<IEmployee, EmployeesMockup>();
}
For More details please visit this URL : https://www.youtube.com/watch?v=aMjiiWtfj2M
for All methods (i.e. AddSingleton vs AddScoped vs AddTransient) Please visit this URL: https://www.youtube.com/watch?v=v6Nr7Zman_Y&list=PL6n9fhu94yhVkdrusLaQsfERmL_Jh4XmU&index=44)
I replaced
services.Add(new ServiceDescriptor(typeof(IMyLogger), typeof(MyLogger)));
With
services.AddTransient<IMyLogger, MyLogger>();
And it worked for me.
I had problems trying to inject from my Program.cs file, by using the CreateDefaultBuilder like below, but ended up solving it by skipping the default binder. (see below).
var host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureServices(servicesCollection => { servicesCollection.AddSingleton<ITest>(x => new Test()); });
webBuilder.UseStartup<Startup>();
}).Build();
It seems like the Build should have been done inside of ConfigureWebHostDefaults to get it work, since otherwise the configuration will be skipped, but correct me if I am wrong.
This approach worked fine:
var host = new WebHostBuilder()
.ConfigureServices(servicesCollection =>
{
var serviceProvider = servicesCollection.BuildServiceProvider();
IConfiguration configuration = (IConfiguration)serviceProvider.GetService(typeof(IConfiguration));
servicesCollection.AddSingleton<ISendEmailHandler>(new SendEmailHandler(configuration));
})
.UseStartup<Startup>()
.Build();
This also shows how to inject an already predefined dependency in .net core (IConfiguration) from
Change BloggerRepository to IBloggerRepository
If you are using dotnet 5 and versions below, you can also check whether you have register the repository in the services.
Adding yet another answer to the fix, because I've been bitten by this one multiple times now. If you create an "Options" class that you're binding to configuration, you might be registering it like this:
// Your options class
public record MyOptions(string SomeSetting);
// ----- 8< -----
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(configuration.GetSection(configPath));
}
}
If you now have a consumer of those options, you'll find that the following code will throw the exception above:
public class OptionsConsumer
{
public OptionsConsumer(MyOptions options)
{
}
}
You have to ask for the "wrapped" version of your options instead:
public class OptionsConsumer
{
public OptionsConsumer(IOptions<MyOptions> options)
{
}
}
For .NET 6.0
I just add this line on Program.cs
builder.Services.AddDbContext<DatabaseContext>();
I was getting this issue when I was working with a 3rd party service - (MassTransit) using the IPublishEndpoint interface in a repo pattern.
End up being that I was calling my services before I was starting MassTransit.
Hope this helps someone...
I am new to .Net Core and trying to get a value from the appsettings.json file but I have missed something very basic. Please let me know what I have done wrong...
Here is the code...
Program.cs
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
})
Startup.cs
public IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
}
Web API Controller
private readonly IConfiguration config;
public EmailController(IConfiguration configuration)
{
if (configuration != null)
{
config = configuration;
}
}
Action Method
var emailTemplatesRelativePath = config.GetSection("EmailSettings");
var email = config.GetValue<string>("Email");
Both the above lines are returning null values for both GetSection and GetValue
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"FCRContext": "server=xxx;database=xxx;user id=xxx;password=xxx"
},
"AllowedHosts": "*",
"EmailSettings": {
"EmailTemplatesPath": "EmailTemplates"
},
"Email": "aa#aa.com"
}
Accessing the configuration in the Controller works a little different than in the Startup.cs. I did this a while back, just follow these steps:
Nuget: Microsoft.Extensions.Configuration.Binder
Put all the Configuration you want to access in your Controller into one Section, e.g. "EmailSettings":
appsettings.json
{
"EmailSettings": {
"EmailTemplatesPath": "EmailTemplates",
"Email": "aa#aa.com"
}
}
Then Create a Class in your Web API Project called EmailSettings.cs :
EmailSettings.cs
public class EmailSettings
{
public string EmailTemplatesPath { get; set; }
public string Email { get; set; }
}
Then bind your Config values to an Instance of the EmailSettings Class in Startup.cs and add that Object to the Dependency Injection Container:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
EmailSettings emailSettings = new EmailSettings();
Configuration.GetSection("EmailSettings").Bind(emailSettings);
services.AddSingleton(emailSettings);
}
Now you can request your Configuration in the Api Controller by simply adding it to the Constructor like this:
Web API Controller
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
EmailSettings _emailSettings;
public ValuesController(EmailSettings emailSettings)
{
_emailSettings = emailSettings;
}
....
}
Just tried this again in my current Project (.NET Core 2.2 Web Api) and it worked. I put a Breakpoint in the ValuesController Constructor and the _emailSettings Object contained the values from the appsettings.json file. You should be able to just Copy & Paste this! Have fun! :)
When hosting in-process inside of IIS (or IIS Express), Directory.GetCurrentDirectory() will return a different path to that which is returned when running out-of-process. Up until ASP.NET Core 2.1, IIS-based hosting was always out-of-process, but ASP.NET Core 2.2 introduces the ability to run in-process (which is the default when creating a new project).
When running out-of-process, Directory.GetCurrentDirectory() will return the path to your ASP.NET Core application itself, whereas when running in-process, it will return the path to IIS (or IIS Express, e.g. "C:\Program Files\IIS Express").
From your question, the relevant code is this:
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
})
Here, before you make a call to SetBasePath, the IConfigurationBuilder has already been set up to use the correct path. Your call itself is overriding this path, setting it to e.g. "C:\Program Files\IIS Express". With this overridden base-path, your appsettings.json et al files are no longer found, as they do not live in e.g. "C:\Program Files\IIS Express", and so no configuration is loaded from these files.
The solution is simply to remove your call to ConfigureAppConfiguration so that the base-path does not get overridden. I realise you've already discovered this, but I wanted to make sure you had an explanation as to what was going wrong here.
You made a simple mistake and forgot to add "Email" into "EmailSettings". Update your json as shown below and get the Email with config.GetSection("EmailSettings")["Email"];
"EmailSettings": {
"EmailTemplatesPath": "EmailTemplates",
"Email": "aa#aa.com"
},
Hope this solves your problem.
Edit:
If you wanna get those values from appsettings to anything other than startup, you should load those config values into a settings class and then inject appropiate IOptions instance to the method constructor in which you want to use those settings. To do so, please see my answer here.
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Startup.cs:
public partial class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.UseMvcWithDefaultRoute();
}
}
Controller Class
You need to use Bind() method to get the value from section.
EmailSetting option = new EmailSetting();
//You need to bind the section with data model,
//it will automatically map config key to property of model having same name
config.GetSection("EmailSettings").Bind(option);
//Alternative to this you can read nested key using below syntax
var emailTemplatesPath = config["EmailSettings:EmailTemplatesPath"];
var emailInEmailSettings = config["EmailSettings:Email"];
// If email key is not nested then you can access it as below
var email = config.GetValue<string>("EmailOutside");
Email Setting Model:- (Property name should match with config Key)
public class EmailSetting
{
public string EmailTemplatesPath { get; set; }
public string Email { get; set; }
}
Appsetting.json:-
{
"AllowedHosts": "*",
"EmailSettings": {
"EmailTemplatesPath": "EmailTemplates",
"Email": "aa#aa.com"
},
"EmailOutside": "aa#aa.com"
}
While others have solved this problem, if you want to leave your startup services code as is, just change your Web API controller to the following:
private readonly EmailSettings _emailSettings;
public EmailController(IOptions<EmailSettings> emailSettings)
{
_emailSettings = emailSettings.Value;
}
The emphasis is on the .Value. That's why your code is returning null. I would also suggest changing program.cs back to it's default. And to use this in an action method, just do the following:
_email.settings.EmailTemplatesPath
One last thing - make sure your EmailSettings class structure is the exact same as your json.
You can do something like this
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
services.AddTransient(p => p.GetRequiredService<IOptions<EmailSettings>>().Value);
and initialize EmailSettings object via constructor DI.
I have implemented an adapter that implement IServiceProvider and returned it from the ConfigureServices method in the Startup. class:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var kernel = new StandardKernel();
var container = new NinjectComponentContainer(kernel);
// ...
return ServiceProviderFactory.Create(container, services);
}
However, my implementation doesn't seem to be used everywhere. I even tried to override the IHttpContextAccessor to return a modified HttpContext:
public HttpContext HttpContext {
get
{
var result = _httpContextAccessor.HttpContext;
result.RequestServices = _serviceProvider;
return result;
}
set => _httpContextAccessor.HttpContext = value;
}
To test whether I could get to my implementation I used a filter in order to see what the HttpContext.RequestServices would return:
public class AuthorizationTestAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var service = context.HttpContext.RequestServices.GetService(typeof(IAccessConfiguration));
}
}
The type returned by context.HttpContext.RequestServices is:
My main issue was trying to get registered components resolved in the constructor of a filter but it always seems to fail saying the component is not registered. However it does seem to work when using the TypeFilter attribute:
[TypeFilter(typeof(RequiresSessionAttribute))]
However, my attribute does inherit from TypeFilter:
public class RequiresSessionAttribute : TypeFilterAttribute
{
public RequiresSessionAttribute() : base(typeof(RequiresSession))
{
Arguments = new object[] { };
}
private class RequiresSession : IAuthorizationFilter
{
private readonly IAccessConfiguration _configuration;
private readonly IDatabaseContextFactory _databaseContextFactory;
private readonly ISessionQuery _sessionQuery;
public RequiresSession(IAccessConfiguration configuration,
IDatabaseContextFactory databaseContextFactory, ISessionQuery sessionQuery)
{
Guard.AgainstNull(configuration, nameof(configuration));
Guard.AgainstNull(databaseContextFactory, nameof(databaseContextFactory));
Guard.AgainstNull(sessionQuery, nameof(sessionQuery));
_configuration = configuration;
_databaseContextFactory = databaseContextFactory;
_sessionQuery = sessionQuery;
}
I did come across this question but there is no definitive answer.
Any ideas on how to correctly provider a custom implementation of the IServiceProvider interface that will be used throughout the solution?
Even though Microsoft states that it is possible to replace the built-in container it appears as though it is not quite this simple, or even possible.
As stated by Steven in his very first comment, if you choose to use your container of choice, you should run them side-by-side.
The guidance from Microsoft suggests changing the ConfigureServices in the Startup class from this:
public void ConfigureServices(IServiceCollection services)
{
// registrations into services
}
to the following:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var container = new YourContainer(); // Castle, Ninject, etc.
// registrations into container
return new YourContainerAdapter(container);
}
However, there are a number of issues with this since there are already framework registrations in services that we do not necessarily know how to re-register in our own container. Well, there is a descriptor so if our container supports all the various methods then it is actually possible to re-register all the components. The various DI containers have different mechanisms when it comes to registration and service resolution. Some of them have a very hard distinction between the two making it quite tricky at times to accommodate a "common" solution.
My initial idea was to provide an adapter that accepts both my own container as well as the services collection from which I would then get the built-in service provider by calling services.BuildServiceProvider(). In this way I could attempt to resolve from the built-in provider and then, if the resolving bit failed, attempt to resolve from my own container. However, it turns out that the .net core implementation does in fact not use the returned IServiceProvder instance.
The only way I could get this to work was to wire up my own container and use it to resolve my controllers. That could be done by providing an implementation of the IControllerActivator interface.
In this particular implementation I was fiddling with Ninject although I typically prefer Castle but the same applies to any DI container:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IKernel>(new StandardKernel());
services.AddSingleton<IControllerActivator, ControllerActivator>();
}
public class ControllerActivator : IControllerActivator
{
private readonly IKernel _kernel;
public ControllerActivator(IKernel kernel)
{
Guard.AgainstNull(kernel, nameof(kernel));
_kernel = kernel;
}
public object Create(ControllerContext context)
{
return _kernel.Get(context.ActionDescriptor.ControllerTypeInfo.AsType());
}
public void Release(ControllerContext context, object controller)
{
_kernel.Release(controller);
}
}
In order to register the controller types I did my DI wiring in the Configure method since I have access to the IApplicationBuilder which can be used to get to the controller types:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
var kernel = app.ApplicationServices.GetService<IKernel>();
// kernel registrations
var applicationPartManager = app.ApplicationServices.GetRequiredService<ApplicationPartManager>();
var controllerFeature = new ControllerFeature();
applicationPartManager.PopulateFeature(controllerFeature);
foreach (var type in controllerFeature.Controllers.Select(t => t.AsType()))
{
kernel.Bind(type).ToSelf().InTransientScope();
}
applicationLifetime.ApplicationStopping.Register(OnShutdown);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(
options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
);
app.UseMvc();
}
This worked swimmingly for the controllers but resolving "filters" was still a problem given that they use the IFilterFactory on the filter itself to implement a factory method:
public IFilterMetadata CreateInstance (IServiceProvider serviceProvider);
Here we can see that the IServiceProvider implementation is provided in order to resolve any depedencies. This applies when using the TypeFilterAttribute or when defining new filters that inherit from TypeFilterAttribute as I have in my question.
This mechanism is actually a very good example of the difference between "Inversion of Control" and "Dependency Injection". The control lies with the framework (inversion) and we have to provide the relevant implementations. The only issue here is that we are not able to hook in properly since our provided IServiceProvider instance is not passed to the CreateInstance method which then results in a failure when attempting to create an instance of the filter. There are going to be a number of ways to fix this design but we'll leave that to Microsoft.
In order to get my filters working I decided to go the "cross-wiring" route as alluded to by Steven by simply registering the depedencies required by my filters in the services collection also:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IKernel>(new StandardKernel());
services.AddSingleton<IControllerActivator, ControllerActivator>();
services.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>();
services.AddSingleton<IDatabaseGateway, DatabaseGateway>();
services.AddSingleton<IDatabaseContextCache, ContextDatabaseContextCache>();
// and so on
}
Since I do not have many dependencies in my filter it works out OK. This does mean that we have "duplicate" registrations that we need to be careful of depending on how the instances are used.
I guess another option may be to forego your DI container of choice and use only the built-in container.
Currently working on a web services project for my class and have decided to make a web API using .NET Core and DynamodDB.
I was just curious what the best way to inject the DynamoDBContext is?
I currently am doing it like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonDynamoDB>();
}
I got this piece of code above from the DynamoDB documentation. I add an instance of IAmazonDynamoDB to the project.
DynamoDBContext context;
public ValuesController(IAmazonDynamoDB context)
{
this.context = new DynamoDBContext(context);
}
In the controller, I then inject the IAmazonDynamoDB instance, and use that to create an instance of DynamoDBContext.
Is there a way to create an instance of the context in the ConfigureServices method and add it to the project there, or is the way I am doing it currently fine?
Is there a way to create an instance of the context in the
ConfigureServices method and add it to the project there, or is the
way I am doing it currently fine?
Although your solution will work, it has a drawback. You're not using Dependency Injection for DynamoDBContext and create its instance in controller constructor through new operator. You'll face a problems when it comes to unit testing your code, because you have no way to substitute implementation of DynamoDBContext.
The proper way is to register DynamoDBContext in DI container and let the container itself create an instance when it's required. With such approach IDynamoDBContext gets injected into ValuesController:
public class ValuesController
{
private readonly IDynamoDBContext context;
public ValuesController(IDynamoDBContext context)
{
this.context = context;
}
// ...
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonDynamoDB>();
services.AddTransient<IDynamoDBContext, DynamoDBContext>();
}
Basically you'd have to create an interface for your DynamoDB context
public interface IDynamoDbContext<T> : IDisposable where T : class
{
Task<T> GetByIdAsync(string id);
Task SaveAsync(T item);
Task DeleteByIdAsync(T item);
}
Create a class implementing the interface
public class DynamoDbContext<T> : DynamoDBContext, IDynamoDbContext<T>
where T : class
{
public DynamoDbContext(IAmazonDynamoDB client)
: base(client)
{
}
public async Task<T> GetByIdAsync(string id)
{
return await base.LoadAsync<T>(id);
}
public async Task SaveAsync(T item)
{
await base.SaveAsync(item);
}
public async Task DeleteByIdAsync(T item)
{
await base.DeleteAsync(item);
}
}
Inject it in your Startup like this
var client = Configuration.GetAWSOptions().CreateServiceClient<IAmazonDynamoDB>();
services.AddScoped<IDynamoDbContext<AwesomeClass>>(provider => new DynamoDbContext<AwesomeClass>(client));
The context will be passed in the DI system and you can use it where you like
private IDynamoDbContext<AwesomeClass> _awesomeContext;
public AwesomeDynamoDbService(IDynamoDbContext<AwesomeClass> awesomeContext)
{
_awesomeContext= awesomeContext;
}
I faced a similar issue and wrote a blog post describing a good way to fix it! Hope it shares some light!
This is how I am using it to make it work with both local & public (AWS hosted) DynamoDB.
appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AWS": {
"Profile": "default",
"Region": "ap-south-1"
},
"DynamoDb": {
"LocalMode": false,
"LocalServiceUrl": "http://localhost:8001",
"TableNamePrefix": ""
}
}
Program.cs in .NET 6
// Get the AWS profile information from configuration providers
AWSOptions awsOptions = builder.Configuration.GetAWSOptions();
// Configure AWS service clients to use these credentials
builder.Services.AddDefaultAWSOptions(awsOptions);
var dynamoDbConfig = builder.Configuration.GetSection("DynamoDb");
var runLocalDynamoDb = dynamoDbConfig.GetValue<bool>("LocalMode");
#region DynamoDB setup
if (runLocalDynamoDb)
{
builder.Services.AddSingleton<IAmazonDynamoDB>(sp =>
{
var clientConfig = new AmazonDynamoDBConfig { ServiceURL = dynamoDbConfig.GetValue<string>("LocalServiceUrl") };
return new AmazonDynamoDBClient(clientConfig);
});
}
else
{
builder.Services.AddAWSService<IAmazonDynamoDB>();
}
builder.Services.AddSingleton<IDynamoDBContext, DynamoDBContext>((serviceProvider) =>
{
IAmazonDynamoDB amazonDynamoDBClient = serviceProvider.GetRequiredService<IAmazonDynamoDB>();
DynamoDBContextConfig dynamoDBContextConfig = new DynamoDBContextConfig
{
TableNamePrefix = dynamoDbConfig.GetValue<string>("TableNamePrefix")
};
return new DynamoDBContext(amazonDynamoDBClient, dynamoDBContextConfig);
});
#endregion
If you are using Lambda then you can try ti use below code
In your DynamoDB database class add constructor with dependency on IDynamoDBContext
public DynamoDbDatabase(IDynamoDBContext dynamoDbContext)
{
_dynamoDbContext = dynamoDbContext;
}
In Function.cs of your Lambda define mapping for dependency injection
private static IServiceProvider ConfigureServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IDynamoDBContext,DynamoDBContext>(p => new DynamoDBContext(new AmazonDynamoDBClient()));
return serviceCollection.BuildServiceProvider();
}
Call above function on top of your lambda function handler
when your code runs it will automatically detect dependency and pass proper DynamoDBContext object when asked for IDynamoDBContext