As you can tell from this question I’m still a newbie with .Net Core and understanding about Dependency Injection.
I’m in the process of writing a .Net Core Console app and I was finally able to get to a point where I’m doing a little bit of DI for logging and configuration settings. What I’m not understanding is using DI when calling another class from within a class.
I created a class called AppHost which has a function called Run() in it. In my Program.cs I’ve setup DI and then will call the AppHost.Run() to execute my main code.
Inside of my AppHost I need to call some database functions in another file I’ve called Data/DataManager. My understanding was that I would setup the class in the DI container and would be able to get my logging and configuration from there. As far as I know, I’ve done that in my “host” declaration. However, when I call my DataManager.GetActiveEmployees() it’s wanting me to create an object since my DataManager is not set a static. When I create a DataManager object it is wanting me to pass in my logger and configuration since that is what is in the constructor of the class. I can do that but is sure seems like that is not the correct way to do it. I thought with DI I would be able to get the logger and configuration out of DI and not need to pass it into the object? Am I supposed to create the DataManager object and pass the logger and configuration from my AppHost into it?
Program.cs
var host = Host.CreateDefaultBuilder().ConfigureServices((context, services) =>
{
services.AddTransient<IAppHost, AppHost>();
services.AddTransient<IFileManager, FileManager>();
services.AddTransient<IDataManager, DataManager>();
services.AddLogging(builder =>
{
builder.AddNLog("nlog.config");
});
}).Build();
var svc = ActivatorUtilities.CreateInstance<AppHost>(host.Services);
svc.Run();
AppHost.cs
private void CheckEmailAddresses()
{
DataManager oDataManager = new DataManager();
var listEmployees = new List<Employee>();
listEmployees = oDataManager.GetActiveEmployees();
}
DataManager.cs
public class DataManager : IDataManager
{
private readonly ILogger<DataManager> _log;
private readonly IConfiguration _configuration;
public DataManager(ILogger<DataManager> log, IConfiguration config)
{
_log = log;
_configuration = config;
}
}
You've already registered your IDataManager in DI dependency. So, instead of doing new which killing the purpose of DI anyway you need to change your CheckEmailAddresses like this.
private void YouClassName()
{
private readonly IDataManager _dataManager;
public YouClassName(IDataManager dataManager)
{
_dataManager = dataManger.
}
private void CheckEmailAddresses()
{
var listEmployees = new List<Employee>();
listEmployees = _dataManager.GetActiveEmployees();
}
}
Now we are inject IDataManager into your class and your other dependencies like Logger will be build automatically.
Related
In Azure Functions (v3 on NetCore3.1) using SimpleInjector 5.3, I've followed the guidance here using IHttpClientFactory instead of a typed client that depends on a MS DI-constructed HttpClient, but SimpleInjector can't resolve the IHttpClientFactory either.
public class FooClient
{
private IHttpClientFactory _clientFactory;
private FooClientConfig _config;
public FooClient(IHttpClientFactory clientFactory, FooClientConfig config)
{
_clientFactory = clientFactory;
_config = config;
}
}
public class Startup : FunctionsStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
var container = new Container();
container.RegisterInstance<FooClientConfig>(new FooClientConfig() { ApiKey = Configuration.GetValue<string>("FooApiKey") });
container.Register<FooClient>();
services.AddSimpleInjector(container);
}
}
public class FooCommandHandler
{
private readonly FooClient _fooClient;
public FooCommandHandler (FooClient client)
{
_fooClient = fooClient;
}
}
I then use the container to activate a Command/Query mediator but the container can't find an IHttpClientFactory to use for the FooClient.
SimpleInjector.ActivationException: 'The configuration is invalid. Creating the instance for type FooClient failed. The constructor of type FooClient contains the parameter with name 'clientFactory' and type IHttpClientFactory, but IHttpClientFactory is not registered. For IHttpClientFactory to be resolved, it must be registered in the container. Verification was triggered because Container.Options.EnableAutoVerification was enabled. To prevent the container from being verified on first resolve, set Container.Options.EnableAutoVerification to false.'
I guess my question is, where am I supposed to setup the Auto-cross-wiring for simpleinjector? I thought the call to services.AddSimpleInjector(container); would make the MS DI registered IHttpClientFactory available to the container and thus to the registrations therein.
Following the integration guide that Steven has put together over at the SimpleInjector docs VERY CAREFULLY, and using IHttpClientFactory instead of any typed clients I was finally able to get this to work - it is not pretty though.
I suggest that if you're using anything other than the Http factory pattern in the old Azure Functions ecosystem (non-isolated) you'll be fine. It's the cross-wiring of certain services like HttpClient under the hood that makes this a total mess. Hope this helps anyone else facing the same issue.
I am writing a dotnet standard library class that will contain an HttpClient that calls a web api method. I want the input/output classes available for ease of use to the caller. The projects consuming the package will be framework apps (4.7)
As far as I can tell the "correct" way to do that is to have a extension method taking an IServiceCollection which registers my client according to some known "defaults" and allows clients to pass in options.
I guess I'm wondering how that would get utilized in a framework app properly? So after I configure the service collection pipeline to know how to do things properly... How does the framework app ever get a ServiceCollection? Do i just make a static class that news up a ServiceCollection and stores the reference? Again to make that as easy as possible would I just include a static factory method in the library class to do that for the consumer? Something like this?
public sealed class DependencyInjectionHelper
{
private static readonly Lazy<DependencyInjectionHelper> _instance = new Lazy<DependencyInjectionHelper>();
public static DependencyInjectionHelper Instance => _instance.Value;
public readonly IServiceCollection ServiceCollection;
public readonly ServiceProvider ServiceProvider;
private DependencyInjectionHelper()
{
ServiceCollection = new ServiceCollection();
ServiceProvider = ServiceCollection.BuildServiceProvider();
}
public MyHttpClient GetClient()
{
return ServiceProvider.GetService<MyHttpClient>();
}
}
public static IServiceCollection AddMyClient(this IServiceCollection services, Action<MyClientOptions, IServiceProvider> configureOptions)
{
services.AddOptions();
services.AddLogging();
services.AddHttpClient<IMyClient, MyClient>().ConfigureHttpClient(client =>
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//configure baseaddress from options here somehow
client.BaseAddress = new Uri("https://someurl:5001/");
if (!AppContext.TryGetSwitch("System.Net.Http.UseSocketsHttpHandler", out var enabled) || !enabled)
{
//https://www.nimaara.com/beware-of-the-net-httpclient/
var servicePoint = ServicePointManager.FindServicePoint(client.BaseAddress);
int desiredLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
if (servicePoint.ConnectionLeaseTimeout != desiredLeaseTimeout)
{
servicePoint.ConnectionLeaseTimeout = desiredLeaseTimeout;
}
}
});
return services;
}
Guess i'm looking for examples in the proper pattern to accomplish this. I understand dependency injection all the way through is probably the best way... but I can't just change monolithic apps to have it. So i want something flexible enough to be used if you have dependency injection, but super simple to consume out of the box.
I created a dotnet core MVC app. When the app starts the first time, I want to load some data from the database to a in-memory cache.
I know how to use the IMemoryCache, to fill data inside and to get them.
However, when the app start, I want to fill the in-memory cache with the data from the database. So I created a Singleton called ReferenceCache and the Interface IReferenceCache.
public interface IReferenceCache
{
public void Setup(IMemoryCache cache);
}
public class ReferenceCache : IReferenceCache
{
private ILogger<ReferenceCache> _logger;
private IMemoryCache _cache;
public void Setup(IMemoryCache cache)
{
//_logger = logger;
_cache = cache;
using (var context = new UtpmvContext())
{
var references = context.Reference.ToList();
_cache.Set("reference", references);
}
}
public List<Reference> GetSomeData()
{
var lsReferences = _cache.Get<List<Reference>>("reference");
List<Reference> liste = lsReferences.FindAll(x => x.ReferenceId == "ANY_ID");
return liste;
}
}
In the ConfigureServices I added my singleton:
services.AddSingleton<IReferenceCache, ReferenceCache>();
Then in the Configure section of the startup file, I added IReferenceCache and IMemoryCache in the signature and call my setup class.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IReferenceCache cache, IMemoryCache memoryCache)
{
// ... code removed for clarity
// I call my service to load the data in the in-memory
cache.Setup(memoryCache);
}
The thing is in my ReferenceCache class, I want to be able to log... so how can I add a dependency of ILogger?
I have never created any service in dotnet core before so please let me know if my design is not correct.
Thank you for your help! :)
You could resolve the ILogger<T> using the IServiceProvider that gets passed to the AddSingleton method:
services.AddSingleton<IReferenceCache>(serviceProvider =>
new ReferenceCache(serviceProvider.GetRequiredService<ILogger<IReferenceCache>>()));
The above registrations replaces services.AddSingleton<IReferenceCache, ReferenceCache>() in ConfigureServices.
You may resolve IMemoryCache the same as you resolve ILogger<IReferenceCache>, i.e. using the GetRequiredService method of the IServiceProvider.
This is documented here by the way.
While running the .Net Core 2.0 API endpoint getting below error.
A suitable constructor for type 'RestDataService' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
public partial class RestDataService : IRestDataService
{
private static HttpClient _client;
private static AppConfiguration _configuration;
private const short MaxRetryAttempts = 3;
private const short TimeSpanToWait = 2;
public RestDataService(AppConfiguration configuration)
{
_client = configuration.HttpClient;
_configuration = configuration;
}
........
And my startup class is something like this :
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var config = new AppConfiguration
{
Environment = Configuration["environment"],
};
services.AddMvc().AddJsonOptions(o => o.SerializerSettings.NullValueHandling = NullValueHandling.Include);
services.AddMemoryCache();
services.AddCors();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddSingleton(Configuration);
services.AddSingleton(config);
services.AddLogging();
services.AddTransient<IRestDataService, RestDataService>();
services.AddHttpClient<IRestDataService, RestDataService>()
.AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOp);
Any suggestions, to get rid of this? constructor is already public and all the parameters are registered in startup file
I received the error "A suitable constructor for type '<type>' could not be located." after accidentally generating a protected constructor instead of public constructor. Marking it back to public fixed it.
For AddHttpClient, you need to provide HttpClient parameter for RestDataService. And, you need to register AppConfiguration.
RestDataService
public class RestDataService: IRestDataService
{
private static HttpClient _client;
private static AppConfiguration _configuration;
private const short MaxRetryAttempts = 3;
private const short TimeSpanToWait = 2;
public RestDataService(AppConfiguration configuration
, HttpClient client)
{
_client = configuration.HttpClient;
_configuration = configuration;
}
}
Startup.cs
var config = new AppConfiguration
{
Environment = Configuration["environment"],
};
services.AddSingleton(typeof(AppConfiguration), config);
services.AddHttpClient<IRestDataService, RestDataService>();
I just ran across this issue because I was accidentally registering HttpClient to an interface.
public void ConfigureServices(IServiceCollection services) {
// !! Wrong, this is done automatically by AddHttpClient<IMyService, MyService>()
services.AddTransient<IMyService, MyService>();
// !! There is no suitable constructor for IMyService, since it is an interface.
services.AddHttpClient<IMyService>();
// Instead, let AddHttpClient manage your service's lifetime,
// and tell it the implementation.
// It is registered with a Transient lifetime as described below.
services.AddHttpClient<IMyService, MyService>();
}
Important context, from source: https://www.stevejgordon.co.uk/ihttpclientfactory-patterns-using-typed-clients-from-singleton-services
When defining typed clients in your ConfigureServices method, the typed service is registered with transient scope. This means that a new instance is created by the DI container every time one is needed. The reason this occurs is that a HttpClient instance is injected into the typed client instance. That HttpClient instance is intended to be short lived so that the HttpClientFactory can ensure that the underlying handlers (and connections) are released and recycled.
You have to define which concrete class of interface you want to use for IRestDataService. So, define like this.
services.AddTransient<IRestDataService, RestDataService>();
Remove static keyword before AppConfiguration.
private readonly AppConfiguration _configuration;
In my case I got this problem when inattentively duplicating row with DI service registration to register new service but accidentally missed that this is .AddHttpClient() method instead of .AddTransient() or .AddScoped()
This doesn't directly answer the question, but is related.
For anyone using ActivatorUtilities.CreateInstance<T>(IServiceProvider, params object[] parameters) it's worth mentioning that every parameter must be in the parameters of the class you are creating. I.e.
public class Example
{
public Example(AppConfiguration configuration)
{
...
}
}
_ = ActivatorUtilities.CreateInstance<Example>(_serviceProvider, new HttpClient())
This will fail as the Example class doesn't have a constructor that has a HttpClient paramter
I had forgotten to add a RequestDelegate next parameter to my middleware constructor. Without this, I would get an exception with the message "A suitable constructor for type [Type] could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor" upon calling IApplicationBuilder.UseMiddleware. My constructor was already public, and my services were all registered. I just needed to add RequestDelegate next to my parameters.
I am doing some dependency injection with Microsoft.Practices.Unity.
For some classes, I am using injection factories like this:
container.RegisterType<ICar>(new InjectionFactory(o => {return new Car("Toyota")}));
Later in my code, I want to be able to find out if I have used or not an injection factory for a given interface.
I see that I can get regitrations in container.Registrations, but these objects do not give me injection members.
A possible way to get them would be to implement a wrapper around my IUnityContainer, that records the injection members.
But maybe there is some better way that directly leverages unity API ? Is there a way to get these injection members directly from the unity container ?
as suggested in my comment (but not with unity). Just copied it from my project.
public void ConfigureServices(IServiceCollection services)
{
var lgr = LogManager.GetCurrentClassLogger();
var logger = new NlogLogger(lgr);
services.AddSingleton<ILogger>(provider => logger);
services.AddSingleton<IMachineConfigFactory, MachineConfigFactory>();
services.AddSingleton<IMemoryCacheService, MemoryCacheService>();
services.AddSingleton<IServerManagerService, ServerManagerService>();
services.AddSingleton<ISubscriberServerHubService, SubscriberServerHubService>();
services.AddSingleton<IPurgeService, PurgeService>();
var configuration = GetConfiguration(option);
services.AddSingleton(configuration);
services.AddOptions();
services.Configure<HostConfig>(configuration.GetSection("HostConfig"));
services.AddSingleton<ServerManager>();
services.AddSingleton<CacheManagerService>();
return services;
}
and then:
public class HealthChecker : IHealthChecker
{
private readonly HealthCheckerConfig _config;
private readonly IAssetProvider _assetProvider;
public HealthChecker(IOptions<HealthCheckerConfig> config, IAssetProvider assetProvider)
{
_config = config.Value;
_assetProvider = assetProvider;
}
}
or am i missing something?