Skip to content

Instantly share code, notes, and snippets.

@bbrt3
Last active July 31, 2021 17:30
Show Gist options
  • Save bbrt3/543551738be183764345db1e706e0dcb to your computer and use it in GitHub Desktop.
Save bbrt3/543551738be183764345db1e706e0dcb to your computer and use it in GitHub Desktop.
Dependency Injection
public interface IClass
{
void TestMethod();
}
public class Class : IClass
{
void TestMethod()
{
Console.WriteLine("LOL");
}
}
public class Startup
{
public void ConfigureService(IServiceCollection services)
{
...
// still need to map it [not sure]
services.TryAddTransient<IClass, Class>();
...
}
}
public class ExampleController
{
public class ExampleController()
{
// we dont use controller dependency injection here!!
}
// what we do instead is inject it inside some method parameter
// using [FromService] decorator
public Method([FromService] IClass Class)
{
IClass _class = Class;
_class.TestMethod();
}
}
/*
Captive dependencies are those in which service uses another service,
but the service that is being used is shorter lived than the one
that is using it, which may cause problems.
That's why selecting correct scopes is important.
Safe dependencies:
TRANSIENT SCOPED SINGLETON
TRANSIENT YES YES YES
SCOPED NO YES YES
SINGLETON NO NO YES
Scope Validation is automoatic check if there are no captive dependencies.
It is enabled by default in development environment and does validation at startup.
*/
/*
POCO (Plan Old CLR Object) is a class that only contains basic properties.
Configuration files usually are POCO classes.
*/
public class ExampleConfig
{
// POCO
public bool EnableX {get; set;}
public bool EnableY {get; set;}
}
appsettings.json
{
"Example":{
"EnableX" : true,
"EnableY" : false
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// reading our configuration section from appsettings.json
// and mapping it to ExampleConfig class object
services.Configure<ExampleConfig>(Configuration.GetSection("Example"));
...
}
}
// accessing configuration from controller
public class ExampleController
{
// property to store our configuration
private readonly ExampleConfig _config;
// getting config data with constructor dependency injection
public class ExampleController(IOptions<ExampleConfig> config)
{
// assigning injected configuration to our config property
_config = config;
}
}
/*
Details should depend upon abstraction, not the other way around.
That's why, when we notice that we use new keyword to create object instance,
we should consider dependency injection instead.
Benefits:
- loose coupling of components
- logical abstraction
- supports unit testing (easier mock-up creation)
- cleaner code
*/
// interface of which implementation
public interface IClass
{
void Test();
}
// implementation of interface
public class Class : IClass
{
void Test()
{
Console.WriteLine("LOL");
}
}
public ClassController : Controller
{
// proparty for holding our implementation object
private readonly IClass _class;
// constructor dependency injection
public ClassController(IClass Class)
{
// assigning injected dependency into our property
_class = Class;
}
public IActionResult Index()
{
...
var viewmodel = new ClassViewModel();
// var currentClass = new Class(); BREAKING DIP RULE!!!
// using our injected dependency instead
// loose coupling refactoring
var currentClass = _class.Test();
...
}
}
public class Startup
{
ConfigureServices(IServiceCollection services)
{
...
// <Interface, Implementation>
// Registering service with Dependency Injection Controller
services.AddTransient(<IClass, Class>)();
...
}
}
/*
Most popualar alternatives are:
a) Scrutor
- assembly scanning
- decorator pattern
b) Autofac
- somehow better for more specialized use
*/
namespace Microsoft.Extensions.DependencyInjection
{
public static class ConfigurationServiceCollectionExtensions
{
public static IServiceCollection AddAppConfiguration(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IClass, ClassA>();
services.AddSingleton<IClass, ClassB>();
services.AddTransient<IClass, ClassC>();
services.AddScoped<IClass, ClassD>();
return services;
}
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddAppConfiguration(configuration);
...
...
}
}
public class IConfig
{
public bool SettingA {get; set;}
public bool SettingB {get; set;}
}
public class ConfigA : IConfig
{
public bool SettingA {get; set;}
public bool SettingB {get; set;}
}
public interface IClass
{
void TestMethod();
}
public class ClassA : IClass
{
private readonly IConfig _config;
public ClassA(IConfig config)
{
_config = config;
}
void TestMethod()
{
Console.WriteLine("LOL");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// injecting correct configuration for given service
services.TryAddSingleton<IConfig>(sp => sp.GetRequiredService<IOptions<ConfigA>>().Value);
...
}
}
/*
There are three lifecycle options:
a) TRANSIENT
Objects are created each time they are requested.
Class1 Class2
-------------------------------
Object1 Object2
------------------------REQUEST
Object3 Object4
------------------------REQUEST
MUST NOT BE THREAD-SAFE
b) SINGLETON
Objects are created once for the lifetime of the application.
Class1 Class2
-------------------------------
Object1 Object1
------------------------REQUEST
Object1 Object1
------------------------REQUEST
MUST BE THREAD-SAFE
IS THREAD-SAFE
c) SCOPED
Objects are created once per request.
Class1 Class2
-------------------------------
Object1 Object1
------------------------REQUEST
Object2 Object2
------------------------REQUEST
MUST NOT BE THREAD-SAFE
*/
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// assigning different scopes to different services
// should depend on our needs
// service that depends on other service shouldn't have shorter lifetime
// than the one that it depends on
services.AddTransient<IServiceA, ServiceA>();
services.AddSingleton<IServiceB, ServiceB>();
services.AddScoped<IServiceC, ServiceC>();
}
}
public class SomeController()
{
// properties for storing our services implementations
private readonly IServiceA _serviceA;
private readonly IServiceB _serviceB;
private readonly IServiceC _serviceC;
// controlle dependency injection
public SomeController(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC)
{
_serviceA = ServiceA;
_serviceB = ServiceB;
_serviceC = ServiceC;
}
}
// some poco interface
public interface IClass
{
public bool IsImportant {get; set;}
}
public class Startup
{
public void ConfigureService(IServiceCollection services)
{
...
// TryAddEnumerable method check if there is already
// no other collection like the one we try to add
// if not it makes it available for injection
services.TryAddEnumerable(new[]
{
ServiceDescriptor.Singleton<IClass, Class>(),
ServiceDescriptor.Singleton<IClass, ClassA>(),
ServiceDescriptor.Singleton<IClass, ClassB>(),
ServiceDescriptor.Singleton<IClass, ClassC>()
}
);
...
}
}
public class SomeController
{
// property for storing collection of injected dependencies
private readonly IEnumerable<IClass> _classes;
// controller dependency collection injection
public class SomeController(IEnumerable<IClass> classes)
{
_classes = classes;
}
public void SomeMethod()
{
// iterating over stored injected dependencies
foreach (var Class in _classes)
{
// checking if some condition is correct
if (Class.IsImportant == true)
{
Console.WriteLine("YES");
}
}
}
}
/*
Registering service multiple times using AddLifetime
will create new entry inside IServiceCollection.
With multiple entires for the same interface,
only the last provided implementation will be used.
Unless we use TryAddLifetime, then the first one
will be used and no other ones will be added.
*/
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// In situations of multiple service regirestrations,
// last registered implementation will be used.
// First one is inside services array but it won't be used!
services.AddSingleton<IClass, Class>();
services.AddSingleton<IClass, AnotherClass>();
// If we want to avoid situations like this we should do
// the same project like that:
services.AddSingleton<IClass, Class>();
// When using TryLifetime, method will check if implementation is already provided
// If it is then it will not add another one.
// If there is no entry, then new one will be added and used.
services.TryAddSingleton<IClass, AnotherClass>();
...
}
}
public class Weather
{
public string Summary {get; set;}
}
public class Person
{
public string Name {get; set;}
}
public interface IClass<T>
{
public void TestMethod();
}
public class ClassA<Weather>
{
public void TestMethod()
{
var weather = new Weather();
Console.WriteLine(Weather.Summary);
}
}
public class ClassA<Person>
{
public void TestMethod()
{
var person = new Person();
Console.WriteLine(Person.Name);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// includes classimplementations for all types that we declare
services.TryAddSingleton(typeof(IClass<>), typeof(ClassA<>));
...
}
}
/*
It is possible to replace provided implementation inside IServiceCollection.
We can also delete all provided implementations for selected interface.
*/
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// Replace example
services.AddSingleton<IClass, Class>();
services.Replace(ServiceDescriptor.Singleton<IClass, AnotherClass>();
// RemoveAll example
services.AddSingleton<IClass, Class>();
services.AddSingleton<IClass, AnotherClass>();
services.RemoveAll<IClass>();
...
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// creating service instance
services.TryAddSingleton<GreetingService>();
// using created instance inside first interface
services.TryAddSingleton<IHomePageGreetingService>(sp =>
sp.GetRequiredService<GreetingService>());
// using created instance inside second interface
services.TryAddSingleton<IGreetingService>(sp =>
sp.GetRequiredService<GreetingService>());
...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment