.NET DI Internal Constructor and Console Logging - c#

I have the following classes in ProjectA:
public class ApplicationBuilder
{
private readonly IServiceCollection _services;
internal ApplicationBuilder() => _services = new ServiceCollection();
public ApplicationBuilder ConfigureServices(Action<IServiceCollection> services)
{
_services.AddSingleton<ILoggerFactory, LoggerFactory>();
_services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
_services
.AddLogging(builder => builder
.AddConsole()
.ClearProviders()
.SetMinimumLevel(LogLevel.Information));
_services.AddSingleton<Application>();
services.Invoke(_services);
return this;
}
public Application Build()
{
var provider = _services.BuildServiceProvider();
return provider.GetRequiredService<Application>();
}
}
[assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyInjection")]
public class Application
{
private readonly ILogger<Application> _logger;
internal Application(ILogger<Application> logger) => _logger = logger;
public static ApplicationBuilder Create() => new();
public void Run()
{
_logger.LogInformation("Application started");
while (true)
{
}
}
}
And the following in ProjectB:
Application.Create()
.ConfigureServices(services =>
{
})
.Build()
.Run();
I get the following exception:
Unhandled exception. System.InvalidOperationException: A suitable constructor for type 'Application' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
I thought [assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyInjection")] would allow DI to construct the type but apparently not. Is there some way to create my own constructor resolver that can use internal constructors?
Also if I skip this problem and make the constructor public (which I don't want to do), the logger doesn't log anything. Am I missing something with the setup for the logger?
Thanks
EDIT: Turns out AddLogging / ClearProviders() was the problem with the logger, I normally do this when using full .NET hosts to clear out the default framework messages but as they aren't here anyways it was clearing out the console logging provider.

Edit: I saw your issue was different, but still, consider this a rewrite for how it can be done.
First of all, remember Application is from System.Windows namespace. So I wouldn't use it. Further below, I'll rewrite the code with some other name.
internal Application(ILogger<Application> logger) => _logger = logger;
Why not removing the internal keyword as a whole alongside the code above? Let's try rewriting it in a way you don't need to do crazy internal witchcraft.
A better approach
Interfaces:
They can be set up in Project B or in a standalone Abstractions project shared by both Project A and Project B.
The following is an interface for configuring services (which returns the second interface when calling ConfigureServices:
/// <summary>
/// Configures the service application and returns the service built.
/// </summary>
/// <typeparam name="TApplication">Application Type</typeparam>
public interface IAppBuilderConfigureServices<TApplication>
where TApplication: class
{
/// <summary>
/// Creates a service injection container.
/// </summary>
/// <param name="services">Opts for configuring services.</param>
/// <returns>App Service Builder</returns>
IAppBuildService<TApplication> ConfigureServices(Action<IServiceCollection> services);
}
Interface for building the service:
/// <summary>
/// Builds the configuration and gets <see cref="TApplication"/> from container.
/// </summary>
/// <typeparam name="TApplication">Application Type</typeparam>
public interface IAppBuildService<TApplication>
where TApplication: class
{
/// <summary>
/// App Service builder that returns Singleton of <see cref="TApplication"/>
/// </summary>
/// <returns>Instance of <see cref="TApplication"/></returns>
TApplication Build();
}
Project A:
internal application builder:
/// <summary>
/// Internally builds the service application and returns the service built.
/// </summary>
/// <typeparam name="TApplication">Application Type</typeparam>
internal class AppBuilder<TApplication> : IAppBuilderConfigureServices<TApplication>, IAppBuildService<TApplication>
where TApplication: class
{
private readonly IServiceCollection _services = new ServiceCollection();
public IAppBuildService<TApplication> ConfigureServices(Action<IServiceCollection> services)
{
_services.AddLogging(s => s.ClearProviders().AddConsole().SetMinimumLevel(LogLevel.Debug));
_services.AddSingleton<TApplication>();
services.Invoke(_services);
return this;
}
public TApplication Build() => _services.BuildServiceProvider().GetRequiredService<TApplication>();
}
public static class AppBuilder
{
/// <summary>
/// Creates an instance of <see cref="IAppBuilderConfigureServices{TApplication}"/>
/// </summary>
/// <typeparam name="TApplication">Application Type</typeparam>
/// <returns>Application builder</returns>
public static IAppBuilderConfigureServices<TApplication> Create<TApplication>()
where TApplication : class =>
new AppBuilder<TApplication>();
}
Project B:
Just of an example of how it MyApp can be configured.
public static class ProjectB
{
public static MyApp Initialize()
{
return AppBuilder.Create<MyApp>()
.ConfigureServices(config =>
{
// ...
})
.Build();
}
}
Finally, your application code:
public class MyApp
{
private readonly ILogger<MyApp> _logger;
public MyApp(ILogger<MyApp> logger) => _logger = logger;
public void HelloWorld()
{
_logger.LogInformation("Hello, World!");
}
}
This is a draft but I think you get the idea. I'm using interfaces to make it more readable, sorry about the summaries, as I wanted to demonstrate what things were going to do.
Usage:
ProjectB.Initialize().HelloWorld();

Related

NopCommerce 4.20 Plugin development error with Dependency Injection

I have a NopCommerce Plugin development with the dBContext name BookAppointmentDBContext and Dependency Registrar DependencyRegistrar see my snippet below.
public class DependencyRegistrar : IDependencyRegistrar
{
private const string CONTEXT_NAME ="nop_object_context_bookappointment";
public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
{
builder.RegisterType<BookAppointmentService>().As<IBookAppointmentService>().InstancePerLifetimeScope();
//data context
builder.RegisterPluginDataContext<BookAppointmentDBContext>(CONTEXT_NAME);
//override required repository with our custom context
builder.RegisterType<EfRepository<CarInspectionModel>>()
.As<IRepository<CarInspectionModel>>()
.WithParameter(ResolvedParameter.ForNamed<IDbContext>(CONTEXT_NAME))
.InstancePerLifetimeScope();
}
public int Order => 1;
}
and BookAppointmentDBContext class below
public class BookAppointmentDBContext : DbContext, IDbContext
{
#region Ctor
public BookAppointmentDBContext(DbContextOptions<BookAppointmentDBContext> options) : base(options)
{
}
/*the other implementation of IDbContext as found in http://docs.nopcommerce.com/display/en/Plugin+with+data+access*/
}
Also, I have a BasePluglin class with
public class BookAppointmentPlugin : BasePlugin
{
private IWebHelper _webHelper;
private readonly BookAppointmentDBContext _context;
public BookAppointmentPlugin(IWebHelper webHelper, BookAppointmentDBContext context)
{
_webHelper = webHelper;
_context = context;
}
public override void Install()
{
_context.Install();
base.Install();
}
public override void Uninstall()
{
_context.Uninstall();
base.Uninstall();
}
}
I keep having this error:
ComponentNotRegisteredException: The requested service 'Microsoft.EntityFrameworkCore.DbContextOption 1[[Nop.Plugin.Misc.BookAppointment.Models.BookAppointmentDBContext, Nop.Plugin.Misc.BookAppointment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
I have BookAppointmentDBContext registered but the error state otherwise.
Any idea what I did wrongly?
This issue is the lack of a registered DbContextOption which is part of the constructor needed to initialize the target db context.
Internally this is what RegisterPluginDataContext does.
/// <summary>
/// Represents extensions of Autofac ContainerBuilder
/// </summary>
public static class ContainerBuilderExtensions
{
/// <summary>
/// Register data context for a plugin
/// </summary>
/// <typeparam name="TContext">DB Context type</typeparam>
/// <param name="builder">Builder</param>
/// <param name="contextName">Context name</param>
public static void RegisterPluginDataContext<TContext>(this ContainerBuilder builder, string contextName) where TContext : DbContext, IDbContext
{
//register named context
builder.Register(context => (IDbContext)Activator.CreateInstance(typeof(TContext), new[] { context.Resolve<DbContextOptions<TContext>>() }))
.Named<IDbContext>(contextName).InstancePerLifetimeScope();
}
}
Source
Note it is trying to resolve DbContextOptions<TContext> when activating the context.
You would need to build the db context options and provide it to the container so that it can be injected into the context when being resolved.
private const string CONTEXT_NAME ="nop_object_context_bookappointment";
public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) {
//...code removed for brevity
var optionsBuilder = new DbContextOptionsBuilder<BookAppointmentDBContext>();
optionsBuilder.UseSqlServer(connectionStringHere);
DbContextOptions<BookAppointmentDBContext> options = optionsBuilder.Options;
builder.RegisterInstance<DbContextOptions<BookAppointmentDBContext>>(options);
//data context
builder.RegisterPluginDataContext<BookAppointmentDBContext>(CONTEXT_NAME);
//...code removed for brevity
}
Reference Configuring a DbContext

Is it possible to instantiate a class which contains an injected enum

Is it possible using Microsoft's DI to inject an enum?
I am getting the following exception when instantiating a class which contains a enum in the constructor.
InvalidOperationException:
Unable to resolve service for type DependencyInjectionWithEnum.Domain.Types.TestType
while attempting to activate DependencyInjectionWithEnum.Domain.Service.TestService
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
I have the following enum:
/// <summary>
/// This is a test enum which is injected into the TestService's constructor
/// </summary>
public enum TestType
{
First,
Second,
Third,
Forth,
Fifth
}
Which gets injected into the following
public class TestService
{
private readonly TestType testType;
/// <summary>
/// Here I am injecting an enum called TestType
/// </summary>
/// <param name="testType"></param>
public TestService(TestType testType)
{
this.testType = testType;
}
/// <summary>
/// This is a dummy method.
/// </summary>
/// <returns></returns>
public string RunTest()
{
switch(testType.ToString().ToUpperInvariant())
{
case "First":
return "FIRST";
case "Second":
return "SECOND";
case "Third":
return "THIRD";
case "Forth":
return "FORTH";
case "Fifth":
return "FIFTH";
default:
throw new InvalidOperationException();
}
}
}
Then in Startup.cs I add the TestService to the ServiceCollection
public void ConfigureServices(IServiceCollection services)
{
//mvc service
services.AddMvc();
// Setup the DI for the TestService
services.AddTransient(typeof(TestService), typeof(TestService));
//data mapper profiler setting
Mapper.Initialize((config) =>
{
config.AddProfile<MappingProfile>();
});
//Swagger API documentation
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "DependencyInjectionWithEnum
API", Version = "v1" });
});
}
Finally I inject my TestService into my controller
[Route("api/[controller]")]
public class TestController : ControllerBase
{
private readonly TestService testService;
/// <summary>
/// Here I am injecting a TestService. The TestService is the class from which I am attempting to inject an enum
/// </summary>
/// <param name="testService"></param>
public TestController(TestService testService)
{
this.testService = testService;
}
/// <summary>
/// Dummy get
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(200, Type = typeof(string))]
public IActionResult Get()
{
var testResult = testService.RunTest();
return Ok(testResult);
}
}
I get the exception when attempting to call the controller's endpoint via Swagger.
Tech Stack
- Visual Studio v15.9.4 C# v7.3
- Project Target Framework .NET Core 2.2
- NuGet Packages
- Microsoft.AspNetCore v2.2.0
- Microsoft.AspNetCore.Mvc v2.2.0
- Microsoft.Extensions.DependencyInjection v2.2.0
Is it possible using Microsoft's DI to inject an enum?
YES
Out of the box the enum can be added with a factory delegate when registering the service at start up
// Setup the DI for the TestService
services.AddTransient<TestService>(sp => new TestService(TestType.First));
When injecting TestService into any dependents the container will use the factory delegate to resolve the class and its dependencies.

Proper way to register HostedService in ASP.NET Core. AddHostedService vs AddSingleton

What is the proper way to register a custom hosted service in ASP.NET Core 2.1? For example, I have a custom hosted service derived from BackgroundService named MyHostedService. How should I register it?
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<IHostedService, MyHostedService>();
}
or
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
services.AddHostedService<MyHostedService>();
}
?
Here we can see the first case, but here there is a second case.
Are these methods equal?
Update
In the past, a HostedService was a long-lived transient, effectively acting as a singleton. Since .NET Core 3.1 it's an actual Singleton.
Use AddHostedService
A hosted service is more than just a singleton service. The runtime "knows" about it, can tell it to start by calling StartAsync or stop by calling StopAsync() whenever eg the application pool is recycled. The runtime can wait for the hosted service to finish before the web application itself terminates.
As the documentation explains a scoped service can be consumed by creating a scope inside the hosted service's worker method. The same holds for transient services.
To do so, an IServicesProvider or an IServiceScopeFactory has to be injected in the hosted service's constructor and used to create the scope.
Borrowing from the docs, the service's constructor and worker method can look like this:
public IServiceProvider Services { get; }
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
private void DoWork()
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork();
}
}
This related question shows how to use a transient DbContext in a hosted service:
public class MyHostedService : IHostedService
{
private readonly IServiceScopeFactory scopeFactory;
public MyHostedService(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public void DoWork()
{
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
…
}
}
…
}
Update
Somewhere between .Net Core 2.2 and 3.1 the behavior has changed, AddHostedService is now adding a Singleton instead of the previous Transient service.
Credit - Comment by LeonG
public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
return services;
}
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services, Func<IServiceProvider, THostedService> implementationFactory)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(implementationFactory));
return services;
}
}
Reference ServiceCollectionHostedServiceExtensions
Original Answer
They are similar but not completely
AddHostedService is part of Microsoft.Extensions.Hosting.Abstractions.
It belongs to Microsoft.Extensions.Hosting.Abstractions in the ServiceCollectionHostedServiceExtensions class
using Microsoft.Extensions.Hosting;
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
=> services.AddTransient<IHostedService, THostedService>();
}
}
Note it is using Transient life time scope and not Singleton
Internally the framework add all the hosted services to another service (HostedServiceExecutor)
public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger,
IEnumerable<IHostedService> services) //<<-- note services collection
{
_logger = logger;
_services = services;
}
at startup that is a singleton via the WebHost Constructor.
_applicationServiceCollection.AddSingleton<HostedServiceExecutor>();
One huge difference is that AddSingleton() is lazy while AddHostedService() is eager.
A service added with AddSingleton() will be instantiated the first time it is injected into a class constructor. This is fine for most services, but if it really is a background service you want, you probably want it to start right away.
A service added with AddHostedService() will be instantiated immediately, even if no other class will ever want it injected into its constructor. This is typical for background services, that run all the time.
Also, it seems that you cannot inject a service added with AddHostedService() into another class.

How do I instiate a class with it's dependencies using Ninject

In my MVC application I have a concept of "scheduled job".
A job inherits from an interface and is then (via reflection) automatically registered on application start.
Now, in my application I am using Ninject for DI to help make my controllers more unit testable. I would love to use ninject for my Scheduled Jobs too but am currently using "poor man's DI" to instantiate my Job's with their dependencies.
So, how exactly would I use Ninject instead of Poor Man's DI?
Job Interface:
public interface IScheduledJob
{
/// <summary>
/// Use Cron method to return frequency expression
/// </summary>
string Frequency { get; }
/// <summary>
/// The entry point of the Job
/// </summary>
void DoWork();
}
Job Init Code:
private void ConfigureSchedules(IAppBuilder app)
{
// Get all types that implement the scheduler interface
IEnumerable<Type> jobs = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IScheduledJob).IsAssignableFrom(p) && !p.IsInterface);
foreach (var jobType in jobs)
{
// Create instance of job
IScheduledJob job = (IScheduledJob)Activator.CreateInstance(jobType);
// Add to schedule
RecurringJob.AddOrUpdate(() => job.DoWork(), job.Frequency);
}
}
Scheduled Job Ctors:
public class SynchronizerScheduledJob : IScheduledJob
{
#region Private Members
private IMagentoSoapService _magentoSoapService;
private IMagentoSoapCredentialProvider _credsProvider;
private IUnitOfWork _unitOfWork;
private IOrderHubService _orderHubService;
#endregion
#region Ctors
public SynchronizerScheduledJob(
IMagentoSoapService service,
IMagentoSoapCredentialProvider credsProvider,
IUnitOfWork unitOfWork,
IOrderHubService orderHubService)
{
_magentoSoapService = service;
_credsProvider = credsProvider;
_unitOfWork = unitOfWork;
_orderHubService = orderHubService;
}
/// <summary>
/// Poor man's DI Ctor
/// </summary>
public SynchronizerScheduledJob() : this(
new MagentoSoapService(),
new MagentoSoapCredentialProvider(),
new UnitOfWork(AppDbContext.Create()),
new OrderHubService())
{ }
}

Use of BaseController even if all dependencies not used?

I have created a base controller with the following constructor:
public BaseController(ICustomer customer, ISiteSettings siteSettings, ILogger logger, ILocalizer localizer)
All my controllers are using the base as they all use all the dependecies. Now I am creating a new controller ErrorController which uses only uses siteSettings and not the other dependencies.
I was wondering if it would make sense to not to use the BaseController as an inheritance in ErrorController and just create a dependency for SiteSettings. On the other hand if the using BaseController is not too expensive, may be I should just keep the consistency with other controllers?
My feelings are since the dependencies are already registered with Unity, hitting the base controller would be a very inexpensive process. Just a thought. Please let me know if you think otherwise.
BaseController Code for your review:
public class BaseController : Controller
{
#region Private Members
/// <summary>
/// Factory to obtain customer object
/// </summary>
protected readonly ICustomer Customer;
/// <summary>
/// Site Settings
/// </summary>
protected readonly ISiteSettings SiteSettings;
/// <summary>
/// The Localization interface
/// </summary>
protected readonly ILocalizer Localizer;
/// <summary>
/// The Logger is used for exception handling
/// </summary>
protected readonly ILogger Logger;
#endregion
#region Ctor
/// <summary>
/// <para>Initializes a new instance of the <see cref="BaseController"/> class.</para>
/// <para>Parameters are injected via Unity.</para>
/// </summary>
/// <param name="customer">The customer.</param>
/// <param name="siteSettings">The site settings.</param>
/// <param name="logger">The logger.</param>
/// <param name="localizer">The localizer.</param>
public BaseController(ICustomer customer, ISiteSettings siteSettings, ILogger logger, ILocalizer localizer)
{
if (customer == null)
{
throw new ArgumentNullException("customer");
}
Customer = customer;
if (siteSettings == null)
{
throw new ArgumentNullException("siteSettings");
}
SiteSettings = siteSettings;
if (logger == null)
{
throw new ArgumentNullException("logger");
}
Logger = logger;
if (localizer == null)
{
throw new ArgumentNullException("localizer");
}
Localizer = localizer;
}
#endregion
}
Unless you have some hairy constructors on those dependencies (not the BaseController constructor itself), this should not be an issue. Your container should handle the grunt work and lifecycle of these. Would use the BaseController for consistency and ease of further expansion.

Categories

Resources