I want to implement dependency injection in ASP.NET CORE 1. I know everything is about DI in .Net Core. For example
public void ConfigureServices(IServiceCollection services)
{
// Add application services.
services.AddTransient<IDateTime, SystemDateTime>();
}
But for Big projects which has more than 20 entities and Services, it is so difficult and unreadable writing all of these code lines inside ConfigureServices. I want to know Is this possible implement dependency injection outside of Startup.cs and then add it to services.
Thanks for answers.
you can write extension methods of IServiceCollection to encapsulate a lot of service registrations into 1 line of code in Startup.cs
for example here is one from my project:
using cloudscribe.Core.Models;
using cloudscribe.Core.Models.Setup;
using cloudscribe.Core.Web;
using cloudscribe.Core.Web.Components;
using cloudscribe.Core.Web.Components.Editor;
using cloudscribe.Core.Web.Components.Messaging;
using cloudscribe.Core.Web.Navigation;
using cloudscribe.Web.Common.Razor;
using cloudscribe.Web.Navigation;
using cloudscribe.Web.Navigation.Caching;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.Extensions.DependencyInjection
{
public static class StartupExtensions
{
public static IServiceCollection AddCloudscribeCore(this IServiceCollection services, IConfigurationRoot configuration)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.Configure<MultiTenantOptions>(configuration.GetSection("MultiTenantOptions"));
services.Configure<SiteConfigOptions>(configuration.GetSection("SiteConfigOptions"));
services.Configure<UIOptions>(configuration.GetSection("UIOptions"));
services.Configure<CkeditorOptions>(configuration.GetSection("CkeditorOptions"));
services.Configure<CachingSiteResolverOptions>(configuration.GetSection("CachingSiteResolverOptions"));
services.AddMultitenancy<SiteContext, CachingSiteResolver>();
services.AddScoped<CacheHelper, CacheHelper>();
services.AddScoped<SiteManager, SiteManager>();
services.AddScoped<GeoDataManager, GeoDataManager>();
services.AddScoped<SystemInfoManager, SystemInfoManager>();
services.AddScoped<IpAddressTracker, IpAddressTracker>();
services.AddScoped<SiteDataProtector>();
services.AddCloudscribeCommmon();
services.AddScoped<ITimeZoneIdResolver, RequestTimeZoneIdResolver>();
services.AddCloudscribePagination();
services.AddScoped<IVersionProviderFactory, VersionProviderFactory>();
services.AddScoped<IVersionProvider, CloudscribeCoreVersionProvider>();
services.AddTransient<ISiteMessageEmailSender, SiteEmailMessageSender>();
services.AddTransient<ISmsSender, SiteSmsSender>();
services.AddSingleton<IThemeListBuilder, SiteThemeListBuilder>();
services.TryAddScoped<ViewRenderer, ViewRenderer>();
services.AddSingleton<IOptions<NavigationOptions>, SiteNavigationOptionsResolver>();
services.AddScoped<ITreeCacheKeyResolver, SiteNavigationCacheKeyResolver>();
services.AddScoped<INodeUrlPrefixProvider, FolderTenantNodeUrlPrefixProvider>();
services.AddCloudscribeNavigation(configuration);
services.AddCloudscribeIdentity();
return services;
}
}
}
and in Startup.cs I call that method with one line of code
services.AddCloudscribeCore(Configuration);
There are several approaches that can be taken, but some are simply moving code between classes; I suggest you consider Assembly Scanning as I describe as the second option below:
1. 'MOVE THE PROBLEM': EXTENSION METHODS
The initial option is to use extension methods for configuration of Services.
Here is one example that wraps multiple service reigstrations into one extension method:
public static IServiceCollection AddCustomServices(this IServiceCollection services)
{
services.AddScoped<IBrowserConfigService, BrowserConfigService>();
services.AddScoped<IManifestService, ManifestService>();
services.AddScoped<IRobotsService, RobotsService>();
services.AddScoped<ISitemapService, SitemapService>();
services.AddScoped<ISitemapPingerService, SitemapPingerService>();
// Add your own custom services here e.g.
// Singleton - Only one instance is ever created and returned.
services.AddSingleton<IExampleService, ExampleService>();
// Scoped - A new instance is created and returned for each request/response cycle.
services.AddScoped<IExampleService, ExampleService>();
// Transient - A new instance is created and returned each time.
services.AddTransient<IExampleService, ExampleService>();
return services;
}
This can be called within ConfigureServices:
services.AddCustomServices();
Note: This is useful as a 'builder pattern', for specific configurations (for example, when a service needs multiple options to be passed to it), but, does not solve the problem of having to register multiple services by hand coding; it is essentially no different to writing the same code but in a different class file, and it still needs manual maintenance.
2. 'SOLVE THE PROBLEM': ASSEMBLY SCANNING
The 'best practice' option is Assembly Scanning which is used to automatically find and Register components based on their Implemented Interfaces; below is an Autofac example:
var assembly= Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
One trick to handle lifetime (or scope) of registration, is to use a marker interface (an empty interface), for example IScopedService, and use that to scan for and register services with the appropriate lifetime. This is the lowest friction approach to registering multiple services, which is automatic, and therefore 'zero maintenance'.
Note: The built in ASP.Net Core DI implementation does not support Assembly Scanning (as pf current, 2016 release); however, the Scrutor project on Github (and Nuget) adds this functionality, which condenses Service and Type registration to:
var collection = new ServiceCollection();
collection.Scan(scan => scan
.FromAssemblyOf<ITransientService>()
.AddClasses(classes => classes.AssignableTo<ITransientService>())
.AsImplementedInterfaces()
.WithTransientLifetime()
.AddClasses(classes => classes.AssignableTo<IScopedService>())
.As<IScopedService>()
.WithScopedLifetime());
SUMMARY:
Assembly Scanning, in combination with Extension Methods (where applicable) will save you a considerable amount of maintenance, and is performed once at application startup, and subsequently cached. It obviates the need to hand code service registrations.
You can write an extension method for batch registration:
public static void AddScopedFromAssembly(this IServiceCollection services, Assembly assembly)
{
var allServices = assembly.GetTypes().Where(p =>
p.GetTypeInfo().IsClass &&
!p.GetTypeInfo().IsAbstract);
foreach (var type in allServices)
{
var allInterfaces = type.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var itype in mainInterfaces)
{
services.AddScoped(itype, type); // if you want you can pass lifetime as a parameter
}
}
}
And usage:
services.AddScopedFromAssembly(assembly);
Add DependenciesManager class to your project and implement AddApplicationRepositories method.
public static class DependenciesManager
{
public static void AddApplicationRepositories(this IServiceCollection service)
{
var assembly = Assembly.GetExecutingAssembly();
var services = assembly.GetTypes().Where(type =>
type.GetTypeInfo().IsClass && type.Name.EndsWith("Repository") &&
!type.GetTypeInfo().IsAbstract);
foreach (var serviceType in services)
{
var allInterfaces = serviceType.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var iServiceType in mainInterfaces)
{
service.AddScoped(iServiceType, serviceType);
}
}
}
}
In Startup class add services.AddApplicationRepositories(); in ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationRepositories();
}
In case you need to register different services, just implement more methods in DependenciesManager class. For example, if you need to register some Authorization Handler services, just implement AddAuthorizationHandlers method:
public static void AddAuthorizationHandlers(this IServiceCollection service)
{
var assembly = Assembly.GetExecutingAssembly();
var services = assembly.GetTypes().Where(type =>
type.GetTypeInfo().IsClass && type.Name.EndsWith("Handler") &&
!type.GetTypeInfo().IsAbstract);
foreach (var serviceType in services)
{
var allInterfaces = serviceType.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var iServiceType in mainInterfaces)
{
service.AddScoped(iServiceType, serviceType);
}
}
}
And in Startup class add:
services.AddAuthorizationHandlers();
Notes: the names of the services and its implementation you want to register must end with "Repository" or "Handler" according to my answer.
I recently implemented the Assembly scanning approach (successfully), but in the end found the cluster_registrations_in_a_few_extension_methods approach a lot clearer to read for myself and for other programmers working on it.
If you keep the clustering of registrations close to where the registered classes are defined, maintenance is always a lot less work than the maintenance involved with the registered classes themselves.
Related
I'm building a small library for internal use that relies on Flurl for handling all outgoing HTTP calls.
I want to provide the ability for consumers of this library to opt-in to our HTTP call tracing infrastrucure like so:
Startup.cs:
...
public void ConfigureServices(IServiceCollection services)
{
....
services.AddTracing(options => ...);
....
}
...
My current implementation of AddTracing() looks like this:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTracing(this IServiceCollection services, Action<TracingOptions> configureOptions)
{
var tracingOptions = new TracingOptions();
configureOptions(tracingOptions);
// Make these options available in DI container
services.AddSingleton(tracingOptions);
FlurlHttp.Configure(settings =>
{
settings.HttpClientFactory = new TracingHttpClientFactory(tracingOptions.ApplicationName);
});
return services;
}
}
And the current implementation of TracingHttpClientFactory looks like this:
public class TracingHttpClientFactory : DefaultHttpClientFactory
{
private readonly string _applicationName;
public TracingHttpClientFactory(string applicationName)
{
_applicationName = applicationName;
}
// override to customize how HttpMessageHandler is created/configured
public override HttpMessageHandler CreateMessageHandler()
{
var tracingHandler = new TracingHandler(_applicationName, base.CreateMessageHandler());
return tracingHandler;
}
}
This works, but the issue I'm facing is that the documentation for Configure() reads: Should only be called once at application startup.
As a result, I've "wasted" my call to Configure() by adding tracing (which is optional). In scenarios where tracing is used, I also still need to call Configure() afterwards.
An example of when I might need to call configure afterwards would be in Startup.cs:
...
public void ConfigureServices(IServiceCollection services)
{
....
// Configure() is being called inside AddTracing()
services.AddTracing(options => ...);
....
// This is a second call to Configure()
FlurlHttp.Configure(settings => {
var jsonSettings = new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
settings.JsonSerializer = new NewtonsoftJsonSerializer(jsonSettings);
});
}
...
Point being - each consumer of AddTracing() should be able to configure Flurl as they see fit. The point of AddTracing() is to simply "supercharge" Flurl with some extra, optional functionality. It isn't meant to take over Configure() - it's meant to extend it.
I've been reading through the docs here, and despite there being a number of places where configuration can occur, I can't work out a way to get my TracingHandler (which is a HttpMessageHander) into every request without calling Configure() somewhere.
Is there a suitable implementation for the scenario I've described?
The reason for the "call once at startup" advice is it touches global scope. If you start messing with the global settings in lots of different places, perhaps on different threads (e.g. in a controller action method), you could end up with weird race conditions and other unpredictable behavior.
In your case, yes, you're calling Configure twice, but the calls are sequential, non-conflicting, and properly done where "startup" code belongs in an ASP.NET Core app. Most importantly, they're done before any calls are made with Flurl. So what you've done here is fine.
I want to remove duplicated singleton service for BuildServiceProvider method. I understand I should use the existing DI service but I can't access the GetService method. I am new to DI and I appreciate it if someone could say how to access the GetService method without getting new service. Code below. Thanks.
MESSAGE: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
public void ConfigureServices(IServiceCollection services){
services.AddAuthorization(options =>
{
var sp = services.BuildServiceProvider();//CODE ISSUE HERE
var permissionService = sp.GetService<IPermissionService>();
if (permissionService != null)
{
foreach (var permission in permissionService.GetPrivilegePermissions().Select(x => x.Name)
.Distinct())
{
options.AddPolicy(permission, policy => policy.Requirements.Add(new
PermissionRequirement(permission)));
}
}
});
}
You have written a lambda method to configure an object. However, since all Microsoft services follow the options pattern, you could instead write a service to configure it. Injecting any other service you want. You may implement any number of IConfigureOptions<T> services for any options type.
public class ConfigureAuthorization : IConfigureOptions<AuthorizationOptions>{
public ConfigureAuthorization( ... ){}
public void Configure(AuthorizationOptions options){
// ...
}
}
services.AddAuthorization();
services.AddSingleton<IConfigureOptions<AuthorizationOptions>, ConfigureAuthorization>();
Note, beware of any potential scoping issues.
I've setup a basic Web Api demonstrate the use of GraphQL using ASP.Net Core. I've Followed a tutorial, what feels like exactly but am getting an error I don't understand.
I'm using GraphQL for .NET v2.4.0
This is the error:
System.InvalidOperationException: No service for type 'Land.GraphQL.Queries.LandQuery' has been registered.
at at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at at GraphQL.FuncDependencyResolver.Resolve(Type type)
at at GraphQL.FuncDependencyResolver.Resolve[T]()
at Land.GraphQL.LandSchema..ctor(IDependencyResolver resolver) in .../LandSchema.cs:11
I'd be grateful for any light shed :)
Here's the code:
I created a LandType:ObjectGraphType to define the Type:
public class LandType : ObjectGraphType<Entities.Land>
{
public LandType(ILandDataAccess landDataAccess)
{
Name = "Land";
Field(land => land.Id, type: typeof(IdGraphType)).Description("Land Id in LandApi context");
Field(land => land.Apn)
.Description(
"Assessor's Parcel Number (APN) is a unique number that is assigned to each tract of land in a county by the Tax Assessor.");
Field(land => land.Address).Description("");
Field(land => land.ZipCode).Description("");
Field(land => land.City).Description("");
Field(land => land.County).Description("");
Field(land => land.State).Description("");
Field(land => land.Country).Description("");
Field(land => land.GisNumber).Description("");
Field(land => land.AssessedValue).Description("");
Field(land => land.LegalDescription).Description("");
Field(land => land.Acreage, type: typeof(FloatGraphType)).Description("Acreage of Land");
}
}
I created a LandQuery:ObjectGraphType to define the Query:
public class LandQuery : ObjectGraphType
{
public LandQuery(ILandDataAccess dataAccess)
{
Field<ListGraphType<LandType>>(
"Land",
resolve: context => dataAccess.GetLandsAsync());
}
}
I created a LandSchema:Schema to define the Schema:
public class LandSchema : Schema
{
public LandSchema(IDependencyResolver resolver) : base(resolver)
{
Query = resolver.Resolve<LandQuery>();
}
}
I added the service and middleware to the Startup file:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(
s.GetRequiredService));
services.AddScoped<LandSchema>();
services.AddGraphQL(o => { o.ExposeExceptions = true; })
.AddGraphTypes(ServiceLifetime.Scoped);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseGraphQL<LandSchema>();
}
Edit:
Thanks to the commenters #NateBarbettini and #TonyNgo inspired me to find the answer. Turns out .AddGraphTypes only searches the calling assembly. My GraphTypes are stored in a referenced Assembly. passing the referenced assembly fixed the problem: .AddGraphTypes(typeof(LandSchema).Assembly, ServiceLifetime.Scoped);
I think you are missing this line of code in your ConfigureServices.
public void ConfigureServices (IServiceCollection services) {
services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver (
s.GetRequiredService));
services.AddScoped<LandSchema>();
services.AddGraphQL(x => {
x.ExposeExceptions = true; //set true only in development mode. make it switchable.
})
.AddGraphTypes (ServiceLifetime.Scoped);
}
If that is not the case you can read my blog here
GraphQL.NET requires you to explicitly register a lot of things. Quick answer: you need to register LandQuery.
You have:
services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
The IDependencyResolver acts as a "glue" between the GraphQL.NET system (which needs to request a lot of service types) and ASP.NET Core's service collection (which contains the service types).
The object graph that you're giving to GraphQL.NET starts with LandSchema, which is correctly registered:
services.AddScoped<LandSchema>();
But look at what LandSchema is doing!
Query = resolver.Resolve<LandQuery>();
This line asks the IDependencyResolver for a LandQuery service type. Your FuncDependencyResolver is pointing at the ASP.NET Core service collection, so this will succeed if LandQuery is registered. It isn't, which is why you are getting the error
No service for type 'Land.GraphQL.Queries.LandQuery' has been registered.
To fix it, add a few new lines to ConfigureServices:
services.AddScoped<LandQuery>();
services.AddScoped<LandType>();
Any type or service (including ILandDataAccess) required by one of the GraphQL types will need to be registered in ConfigureServices. If you don't want to hand-register every single GraphQL.NET type, use Scrutor to auto-register types that derive from GraphType:
// Add all classes that represent graph types
services.Scan(scan => scan
.FromAssemblyOf<LandQuery>()
.AddClasses(classes => classes.AssignableTo<GraphQL.Types.GraphType>())
.AsSelf()
.WithSingletonLifetime());
EDITED:
Some months ago I created a .net core graphql project.
In the real-life, you need to tune the original library.
I want to share some tricks and common classes that help you to build a .net core graphql solution.
For example, I've created an automapper that automatically convert your dto model into a ObjectGraphType (output) and InputObjectGraphType (input).
But not only this.
For example you can't use efficiently dbcontext if you register it using AddDbContext (scoped) like a classical .net core web api, because you need to register Graphql middleware as singleton, so AddDbContext is a "fake scoped" because it is used inside a "singleton middleware".
This means that context using AddDbContext is instantiated just once at startup!
So, you will have problem of simultaneous access to the connection.
But I have solution: use factory pattern.
If you want a complete code example, see here:
https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695
I am using VS 2017 and .NET Core.
Using Dependency Injection, I would like to register my service at runtime, dynamically. My goal is to write instances of my service that implement the service interface inside of separate assemblies. The servicename/assembly name will then be added to some sort of configuration file (or db table).
My registration code would do something like this:
var ServiceTypeName = LoadServiceAssembly(AssemblyName);
var serviceProvider = new ServiceCollection()
.AddTransient<IDILogger, "ConsoleDILogger">() // <--- Goal
.BuildServiceProvider();
var logger = serviceProvider.GetService(IDILogger);
Clearly, the AddTransient line will not work as such a method does not exist. It does, however, depict the idea. I want to register the type by a string name so that the loader application need not be recompiled everytime I add a new service type.
I cannot seem to find how to do this. Any suggestions would be welcome.
TIA
You could read configured type from the settings, load the required type via reflection and register it in service collection:
// Read from config
var assemblyPath = "...";
var typeName = "...";
var assembly = Assembly.LoadFrom(assemblyPath);
var loggerType = assembly.GetType(typeName);
var serviceProvider = new ServiceCollection()
.AddTransient(typeof(IDILogger), loggerType)
.BuildServiceProvider();
var logger = serviceProvider.GetService<IDILogger>();
Such dynamic approach will not require any recompilation if you add or reconfigure new logger.
That's obviously not possible as is, however, I used something similar to this in a project to avoid having to add each new type to the container:
var assembly = typeof(YourClass).Assembly; // I actually use Assembly.LoadFile with well-known names
var types = assembly.ExportedTypes
// filter types that are unrelated
.Where(x => x.IsClass && x.IsPublic);
foreach (var type in types)
{
// assume that we want to inject any class that implements an interface
// whose name is the type's name prefixed with I
services.AddScoped(type.GetInterface($"I{type.Name}"), type);
}
For your specific case, you could even make this shorter:
var type = assembly.ExportedTypes.First(x => x.Name == runtimeName);
services.AddScoped(typeof(IDILogger), type);
A very genuine question and with references to different answers by users, here's how I have solved in .NET 6
In program.cs added the following
//Register Service Modules to DI
builder.Services.IncludeServiceModule(builder.Configuration);
The called static function contains something like this
public static class ServiceModule
{
public static IServiceCollection IncludeServiceModule(this IServiceCollection services,
IConfiguration configuration)
{
var appServices = System.Reflection.Assembly.Load("FMDeBill.Service").GetTypes().Where(s => s.Name.EndsWith("Service") && s.IsInterface == false).ToList();
foreach (var appService in appServices)
//services.AddTransient(appService.GetInterface($"I{appService.Name}"), appService);
services.Add(new ServiceDescriptor(appService, appService, ServiceLifetime.Scoped));
return services;
}
}
The assembly name is the name of the project/assembly with services. Any service that is not an interface and ends with "Service" such as "CategoryService" is registered dynamically.
Auto-Register Dependency Injected Services in .NET Core
I wrote this method to auto-register all your services and consumer interfaces and classes at runtime for Dependency Injection by the IoC Container in .NET. All you have to do is add your interfaces and/or concrete classes to the enums lists below and the RegisterServices() method will add them for dependency injection in your .NET application. You can then add them to constructors or call them for dependency injection by .NET.
I chose to load services from an enum rather than say a JSON or other configuration file for security reasons. It also reduces dependencies and also locks the applications state, as well as forces development to lock the app to compilation. Developers must modify, add, remove service types and keep them closely coupled to the code. Changing a configuration file is too dangerous!
LET'S BEGIN
You will need to create two files then change the Startup.cs file in .NET.
Create a file called ServiceList.cs in .NET. This one is just a couple enums where you can add your list of types you want registered as services or consumers of services. If you have many classes that inherit from an Interface, just add lists of those in services. But it will accept concrete types, as well. But if you add an interface, the RegisterServices method below will locate all the child classes that implement the interface and register those, as well. The RegisterServices() method will grab them and register all your services with the IoC in .NET for you.
// ADD SERVICES YOU WANT REGISTERED
enum ServicesList
{
ISampleService,
IAnotherService,
AConcreteClassService
}
// ADD CONSUMERS YOU WANT REGISTERED
enum ConsumersList
{
MyClass1,
MyClass2,
ISomeConsumerTypes
}
Create a second class file called RegisterServices.cs. Add the following code. This is the main method that registers all the services listed in the enums above. It is called RegisterServices.cs.
// REGISTER SERVICES
// This will pull all the services you added to the ServicesList.cs
// enum and try and register them with the Services Provider in .NET
static class RegisterServices
{
// You can add the Logger here if you like.
internal static void Start(IServiceCollection services, ILogger logger = null)
{
// Extract out all service enum values into a single list.
List<string> allTypesToAdd = new List<string>();
allTypesToAdd.AddRange(Enum.GetNames(typeof(ServicesList)).ToList());
allTypesToAdd.AddRange(Enum.GetNames(typeof(ConsumersList)).ToList());
// For now I am just getting the active running assembly
Assembly assembly = Assembly.GetExecutingAssembly();
IEnumerable<TypeInfo> assemblyTypes = assembly.DefinedTypes;
List<string> missingEnumTypes = new List<string>();
bool isTypeFound = false;
// Loop through all services in the collection.
// If your service type is not listed, add it.
foreach (string typeToAdd in allTypesToAdd)
{
// Verify the enum type to add to the service collection exists in the application.
isTypeFound = false;
foreach (TypeInfo type in assemblyTypes)
{
if (type.Name == typeToAdd)
{
if (type.IsInterface)
{
// Add the Interface and any concrete classes
// that are implementations of the parent interface.
var childOfInterface = assembly.GetTypes().Where(t => type.AsType().IsAssignableFrom(t));
foreach (Type c in childOfInterface)
{
if (typeToAdd != c.Name)
{
// For now this just assumes you need a request
// scoped service lifetime services. Change as needed.
services.TryAddScoped(type.AsType(), c);
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + "," + c.Name + ")");
}
}
} else {
// Only add the concrete class
// For now just use scoped service lifetime
services.TryAddScoped(type.AsType());
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + ")");
}
isTypeFound = true;
break;
}
}
// If users added types in the enum lists
// thats not found, flag as a warning!
if (!isTypeFound)
{
missingEnumTypes.Add(typeToAdd);
}
}
// If a bad enum service name was added, log that as a warning.
if (missingEnumTypes.Count > 0)
{
string items = string.Empty;
foreach (string s in missingEnumTypes)
{
if (items != string.Empty) items += " | ";
items += s;
}
logger?.LogWarning(LogEventIDs.Warning_General_ID, "WARNING: These Types/Interfaces/Classes added to Services were not found in the application >>> " + items);
}
}
}
Register Services consumes the enum list of Services and Consumers above.
The last step is to call the method above inside your Startup.cs .NET file in Core. Add RegisterServices.Start() static method call with your ConfigureServices class inside Startup.cs in the root of your .NET Core application. I also add the logger as a parameter but this version just use the services parameter. "services" is whatever the parameter is in your
ConfigureServices method in Startup.cs:
RegisterServices.Start(services);
HOW TO USE DEPENDENCY INJECTION
After you run RegisterServices in your .NET application and Startup.cs calls it, all your services (and child classes derived from interfaces) are now registered!
To call a Service and have it auto-implemented when you instantiate a class in .NET appears to be inconsistent. The IoC Container will auto-inject all constructor services in MVC Controllers, for example, but NOT regular classes. To solve that I recommend you try and inject everything into your controllers, then use the IServiceProvider in regular class constructors to help you auto-inject all other classes with the services they need (see below).
If you are in ASP.NET Core, your best strategy is to ALWAYS add each service to your controller's constructor using interfaces. You can then have full access to every service you need or any service a child object inside the controller might need. But there will be times you have classes you call outside the controllers that inject services but are not auto-injected. So below are some examples of how to do that and still honor the dependency injection model.
Note: If you are an expert at this, please suggest below in comments how I can improve on this idea, as this is the best model I have for now that is simple and easy to use.
// HOW TO USE SERVICES?
// CONTROLLERS (Web Applications)
// Always inject the services you need into the controller's constructor.
// The IoC Container in .NET always auto-injects these objects
// for you and are 100% ready to access. If using ASP.NET, always use the
// constructor of the controller to inject services.
public class HomeController : BaseController
{
private readonly ISampleService _myservice;
public HomeController(ISampleService myservice){
_myservice = myservice;
}
// You can now access your "_myservice" in any action method of the controller
}
// NON-CONTROLLERS and NON-INJECTED CONSTRUCTORS
// If you cant inject the service object into an ASP.NET Controller
// but still need to instantiate the object, your best alternative
// is to inject the ServiceProvider into your Controller or Class
// constructor first. IoC auto-injects the service collection
// so you can now access it to create child objects you can
// tell .NET to auto-inject with their own services when created
// using the registered services in your enum as an example.
public MyClass (IServiceProvider myservice) {
// Here are 3 ways to force the IoC to auto-inject your dependencies
var obj1 = myservice.GetService<SampleService>();
var obj2 = myservice.GetService(SampleService) as ISampleService;
var obj3 = myservice.GetRequiredService(SampleService) as ISampleService;
var obj4 = (SampleService)myservice.GetService(typeof(SampleService));
}
Below is one of the Service Interface types in the enum above and the child classes that got registered which are now available to use as services in the code above after running the RegisterServices call:
// SERVICE INTERFACE
public interface ISampleService
{
void Message(string message);
}
// SERVICE CONCRETE CLASS
class SampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
// SERVICE CONCRETE CLASS
class AnotherSampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
You can use factory to achieve that.
services.AddScoped(provider =>
{
//Resolve some service at runtime.
var aService = provider.GetService<AServiceType>();
//Any synchronous logic here
return new MyDynamicService();
});
We have a Web API project and using the Autofac Web API Integration as the IoC container. The code that we use to register all of our types is as follows:
public class CompositionRootConfigurator
{
public static AutofacWebApiDependencyResolver Configure(Assembly servicesAssembly)
{
var container = BuildContainer(servicesAssembly);
var resolver = new AutofacWebApiDependencyResolver(container);
return resolver;
}
public static IContainer BuildContainer(Assembly servicesAssembly)
{
/*TO DELETE ONCE THE REFERENCES ISSUE IS RESOLVED!*/
var dummy = new EmployeesBL(new ContextFactory(new DBContextFactory(new RoleBasedSecurity(), new Identity())));
var builder = new ContainerBuilder();
if (servicesAssembly != null) // this is a temporary workaround, we need a more solid approach here
{
builder.RegisterApiControllers(servicesAssembly);
}
/* Registers all interfaces and their implementations from the following assemblies in the IoC container
* 1. CB.CRISP.BL
* 2. CB.CRISP.BL.CONTRACTS
* 3. CB.CRISP.DAL
* 4. CB.CRISP.DAL.CONTRACTS
* The current assembly is excluded because the controllers were registered with the builder.RegisterApiControllers expression above.
*/
var appAssemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => a.ToString().StartsWith("CB.CRISP"))
.ToArray();
builder.RegisterAssemblyTypes(appAssemblies).AsImplementedInterfaces();
if (servicesAssembly != null)
{
builder.RegisterAssemblyTypes(servicesAssembly).AsImplementedInterfaces();
}
return builder.Build();
}
}
Now suppose we have a MyType which implements IMyType and this is the only one that must be a single instance per request and it will be injected in several objects along the hierarchy.
I am at a loss in how to specify this within this existing code. If I just go ahead and just do
builder.RegisterType<MyType>()
.As<IMyType>()
.InstancePerRequest();
since it will also be registered with all the others Will one registration overwrite the other one, will they be duplicated, are there potential problems?
Thank you for your insight.
Autofac will override the first registration and will accept the last one. Here is more detail.
So you should register MyType after registering all type.
I haven't seen any potential problem of this.
But you can register all types like this to be sure.
builder.RegisterAssemblyTypes(servicesAssembly).Except<MyType>().AsImplementedInterfaces();
Here is more detail about scanning.