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.
Related
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();
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
How can we access the ParitionInfo object in a Controller class of a stateful service?
Here is the link to the object: https://learn.microsoft.com/en-us/dotnet/api/system.fabric.servicenotification.partitioninfo?view=azure-dotnet
public class MyConntroller : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Save(MyObject obj)
{
//Here I would like to access ParitionInfo object. How?!
}
}
And here is the service definition in the ASP.NET Core stateful service where the object can easily be obtained from the base class where it's defined:
/// <summary>
/// The FabricRuntime creates an instance of this class for each service
/// type instance.
/// </summary>
internal sealed class MyStatefulService : StatefulService
{
public MyStatefulService(StatefulServiceContext context)
: base(context)
{ }
/// <summary>
/// Optional override to create listeners (like tcp, http) for this service instance.
/// </summary>
/// <returns>The collection of listeners.</returns>
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return new ServiceReplicaListener[]
{
new ServiceReplicaListener(serviceContext =>
new KestrelCommunicationListener(serviceContext, (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices(
services => services
.AddSingleton<StatefulServiceContext>(serviceContext)
.AddSingleton<IReliableStateManager>(this.StateManager))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
.UseUrls(url)
.Build();
}))
};
}
Should I create a singleton class, wire it up through DI, and have the DI framework pass its instance to the controller class? Are there better shortcuts to achieve the goal of accessing ParitionInfo data?
You're on the right path. Add an argument IStatefulServicePartition to your MyConntroller constructor.
In ConfigureServices, register the service partition using this.Partition.
For example:
.AddSingleton<IStatefulServicePartition>(this.Partition)
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.
Creating Dependency Injection with ASP.NET Core is fairly easy. The documentation explains it very well here and this guy has a killer video to explain it.
However, I want to do the same thing with my ASP.NET MVC 5 project. How can handle dependency injection with ASP.MVC 5?
Also, is Dependency injection limited to controllers only or can it work with any class?
In ASP.Net MVC you can use the .Net Core DI from NuGet rather than one of the third-party alternatives:-
using Microsoft.Extensions.DependencyInjection
For the MVC Start/Configuration class:-
public void Configuration(IAppBuilder app)
{
// We will use Dependency Injection for all controllers and other classes, so we'll need a service collection
var services = new ServiceCollection();
// configure all of the services required for DI
ConfigureServices(services);
// Configure authentication
ConfigureAuth(app);
// Create a new resolver from our own default implementation
var resolver = new DefaultDependencyResolver(services.BuildServiceProvider());
// Set the application resolver to our default resolver. This comes from "System.Web.Mvc"
//Other services may be added elsewhere through time
DependencyResolver.SetResolver(resolver);
}
My project uses Identity User and I've replaced the OWIN start-up configuration to follow a service-based approach instead. The default Identity User classes use static factory methods to create instances. I've moved that code into the constructors and relied on DI to provide the appropriate injection. It is still work in progress but here is where I am at:-
public void ConfigureServices(IServiceCollection services)
{
//====================================================
// Create the DB context for the IDENTITY database
//====================================================
// Add a database context - this can be instantiated with no parameters
services.AddTransient(typeof(ApplicationDbContext));
//====================================================
// ApplicationUserManager
//====================================================
// instantiation requires the following instance of the Identity database
services.AddTransient(typeof(IUserStore<ApplicationUser>), p => new UserStore<ApplicationUser>(new ApplicationDbContext()));
// with the above defined, we can add the user manager class as a type
services.AddTransient(typeof(ApplicationUserManager));
//====================================================
// ApplicationSignInManager
//====================================================
// instantiation requires two parameters, [ApplicationUserManager] (defined above) and [IAuthenticationManager]
services.AddTransient(typeof(Microsoft.Owin.Security.IAuthenticationManager), p => new OwinContext().Authentication);
services.AddTransient(typeof(ApplicationSignInManager));
//====================================================
// ApplicationRoleManager
//====================================================
// Maps the rolemanager of identity role to the concrete role manager type
services.AddTransient<RoleManager<IdentityRole>, ApplicationRoleManager>();
// Maps the role store role to the implemented type
services.AddTransient<IRoleStore<IdentityRole, string>, RoleStore<IdentityRole>>();
services.AddTransient(typeof(ApplicationRoleManager));
//====================================================
// Add all controllers as services
//====================================================
services.AddControllersAsServices(typeof(Startup).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition)
.Where(t => typeof(IController).IsAssignableFrom(t)
|| t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)));
}
The Account Controller class has the single constructor:-
[Authorize]
public class AccountController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
private RoleManager<IdentityRole> _roleManager;
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, RoleManager<IdentityRole> roleManager)
{
UserManager = userManager;
SignInManager = signInManager;
RoleManager = roleManager;
}
}
My Default Dependency Resolver:
/// <summary>
/// Provides the default dependency resolver for the application - based on IDependencyResolver, which hhas just two methods
/// </summary>
public class DefaultDependencyResolver : IDependencyResolver
{
/// <summary>
/// Provides the service that holds the services
/// </summary>
protected IServiceProvider serviceProvider;
/// <summary>
/// Create the service resolver using the service provided (Direct Injection pattern)
/// </summary>
/// <param name="serviceProvider"></param>
public DefaultDependencyResolver(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
/// <summary>
/// Get a service by type - assume you get the first one encountered
/// </summary>
/// <param name="serviceType"></param>
/// <returns></returns>
public object GetService(Type serviceType)
{
return this.serviceProvider.GetService(serviceType);
}
/// <summary>
/// Get all services of a type
/// </summary>
/// <param name="serviceType"></param>
/// <returns></returns>
public IEnumerable<object> GetServices(Type serviceType)
{
return this.serviceProvider.GetServices(serviceType);
}
}
For this answer I downloaded a Microsoft Example of WebApi project as a basis for the example and added DI services to it as follows,
Update the Target Framework to 4.6.1
NuGet the DI package :- Microsoft.Extensions.DependencyInjection
After the standard MapHttpRoute configuration, add code to register which services you need
using's
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Web.Http.Dependencies;
using ProductsApp.Controllers;
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// create the DI services and make the default resolver
var services = new ServiceCollection();
services.AddTransient(typeof(DefaultProduct));
services.AddTransient(typeof(ProductsController));
var resolver = new MyDependencyResolver(services.BuildServiceProvider());
config.DependencyResolver = resolver;
}
}
DefaultProduct
public class DefaultProduct : ProductsApp.Models.Product
{
public DefaultProduct()
{
this.Category = "Computing";
this.Id = 999;
this.Name = "Direct Injection";
this.Price = 99.99M;
}
}
MyDependencyResolver
/// <summary>
/// Provides the default dependency resolver for the application - based on IDependencyResolver, which hhas just two methods.
/// This is combined dependency resolver for MVC and WebAPI usage.
/// </summary>
public class MyDependencyResolver : System.Web.Mvc.IDependencyResolver, System.Web.Http.Dependencies.IDependencyResolver
{
protected IServiceProvider serviceProvider;
protected IServiceScope scope = null;
public MyDependencyResolver(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public MyDependencyResolver(IServiceScope scope)
{
this.scope = scope;
this.serviceProvider = scope.ServiceProvider;
}
public IDependencyScope BeginScope()
{
return new MyDependencyResolver(serviceProvider.CreateScope());
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
scope?.Dispose();
}
public object GetService(Type serviceType)
{
return this.serviceProvider.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.serviceProvider.GetServices(serviceType);
}
}
ServiceProviderExtensions
public static class ServiceProviderExtensions
{
public static IServiceCollection AddControllersAsServices(this IServiceCollection services, IEnumerable<Type> serviceTypes)
{
foreach (var type in serviceTypes)
{
services.AddTransient(type);
}
return services;
}
}
I then amended the existing controller to take the DI type (note there is just the one ctor)
using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
DefaultProduct _dp = null;
public ProductsController(DefaultProduct dp)
{
_dp = dp;
//
products.Add(dp);
}
List<Product> products = new List<Product>()
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};
public IEnumerable<Product> GetAllProducts()
{
return products;
}
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
}
My Default Dependency Resolver
/// <summary>
/// Provides the default dependency resolver for the application - based on IDependencyResolver, which hhas just two methods
/// </summary>
public class DefaultDependencyResolver : IDependencyResolver
{
/// <summary>
/// Provides the service that holds the services
/// </summary>
protected IServiceProvider serviceProvider;
/// <summary>
/// Create the service resolver using the service provided (Direct Injection pattern)
/// </summary>
/// <param name="serviceProvider"></param>
public DefaultDependencyResolver(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
/// <summary>
/// Get a service by type - assume you get the first one encountered
/// </summary>
/// <param name="serviceType"></param>
/// <returns></returns>
public object GetService(Type serviceType)
{
return this.serviceProvider.GetService(serviceType);
}
/// <summary>
/// Get all services of a type
/// </summary>
/// <param name="serviceType"></param>
/// <returns></returns>
public IEnumerable<object> GetServices(Type serviceType)
{
return this.serviceProvider.GetServices(serviceType);
}
}
I recommend you use Autofac, there are anothers fwk like unity, ninject, the benchmarks autofac has excelent perfomance.
http://www.palmmedia.de/blog/2011/8/30/ioc-container-benchmark-performance-comparison
Here is the integration with MVC (and works with all class)
http://docs.autofac.org/en/latest/integration/mvc.html
The simplest way to implements Dependency Injection in ASP.NET MVC 5 is to use the tool developed by Microsoft itself, called Unity.
You can find many resources on the internet about it, and you can start by reading the official documentation available here: Developer's Guide to Dependency Injection Using Unity
Also, is Dependency injection limited to controllers only or can it work with any class?
It works with any class, in any project, as long as you register the Interface related to the Implementation (if you want to take profit of the IoC pattern), all you have to do then is to add the Interface instantiation in your constructor.
In this video a Microsoft MVP demos dependency injection in MVC5 with AutoFac. Very clear explanation on how to set it up:
Dependency Injection MVC5 Demo
Source code is available on GitHub
From here https://scottdorman.blog/2016/03/17/integrating-asp-net-core-dependency-injection-in-mvc-4/
this line saved me.
services.AddControllersAsServices(typeof(Startup).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition)
.Where(t => typeof(IController).IsAssignableFrom(t)
|| t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)));
I recommend using Windsor, by installing the nuget package Castle Windsor MVC Bootstrapper, then you can create a service that implements IWindsorInstaller, something like this:
public class ServiceRegister : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container,
Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
SomeTypeRequiredByConstructor context = new SomeTypeRequiredByConstructor ();
container.Register(
Component
.For<IServiceToRegister>()
.ImplementedBy<ServiceToRegister>().
DependsOn(Dependency.OnValue<SomeTypeRequiredByConstructor>(context))//This is in case your service has parametrize constructoe
.LifestyleTransient());
}
}
Then inside your controller something like this:
public class MyController
{
IServiceToRegister _serviceToRegister;
public MyController (IServiceToRegister serviceToRegister)
{
_serviceToRegister = serviceToRegister;//Then you can use it inside your controller
}
}
And by default the library will handle sending the right service to your controller by calling the install() of ServiceRegister at start up because it implements IWindsorInstaller
Having started from this thread to figure out how to use Microsoft.Extensions.DependencyInjection in my ASP.NET MVC 5 project, and reading and trying and failing, I finally came up with a solution that I wanted to shamelessly offer to the rest of you.
I pieced together a gist from David Fowler, the example code from Scott Dorman, and added in a bit of my own spice to create library that allows you to simulate ASP.NET Core's Startup in ASP.NET MVC "Classic".
For more information, please take a look at the GitHub repository for Arex388.AspNet.Mvc.Startup. If you're interested you can also read through my blog post about it, here (if it doesn't load, refresh until it does, the server's been giving me troubles and I haven't had time to investigate...). Hope it helps someone!