This is the continuation of [Part1]. If you did not read it, please go to the first link and then come here. In the first article, I explain the problem of how to avoid service registration. To solve the issue, we will use an open generic.
As per the Microsoft
A generic type or method is closed if instantiable types have been substituted for all its type parameters, including all the type parameters of all enclosing types. You can only create an instance of a generic type if it is closed.
So in simple terms List<int>
is closed and List<>
is open generics
Why open generic DI
Open generic registration makes our software extensible. This means you can extend the behavior of your software with minimal changes
Let’s extend the previous code to support the Open Generic
feature. To support Open Generic
first, we need to generic Interface, as shown below.
IAduditLog.cs
public interface IAuditLog
{
void Log(string action);
}
IAuditLog
public interface IAuditLog<out T> : IAuditLog {
}
Why Generic Interface
A generic interface used to enable activation of
[IAudit].
Why out keyword in Interface declaration?
This type of parameter is covariant. That is, you can use either the type you specified or any more derived type.
After that, you have to implement a AuditLogManager
generic class to register in the DI container.
AuditLogManager
public class AuditLogManager<T> : IAuditLog<T>
{
private readonly IAuditLog _auditLog;
public AuditLogManager()
{
_auditLog = new ConsoleAuditLog(typeof(T).Name);
}
public void Log( string action)
{
_auditLog.Log(action);
}
}
ConsoleAuditLog
public class ConsoleAuditLog : IAuditLog
{
public string SourceName { get; }
public ConsoleAuditLog(string sourceName)
{
SourceName = sourceName;
}
public void Log(string action)
{
Console.WriteLine($"{SourceName}- Logging {action} to Console");
}
}
Now it’s time to introduce. Open Generic.
To register open generic, you have to use the following syntax.
.AddScoped(typeof(IAuditLog<>),typeof(AuditLogManager<>))
You can see I am using a non-generic version of AddScoped. Your open generic interface and implementation should be specified using a type of keyword.
public class Startup
{
public static ServiceProvider Configure()
{
var provider = new ServiceCollection()
.AddSingleton<ICustomerService, CustomerService>()
.AddScoped(typeof(IAuditLog<>),typeof(AuditLogManager<>))
.AddLogging(fs => fs.AddConsole())
.BuildServiceProvider(validateScopes: false);
return provider;
}
}
How to use
Let’s suppose you want to use the AuditLog in your application, then you simply need to Inject
the audit logs into your code.
Customer.cs
public class Customer
{
public int Id { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
ICustomerService.cs
public interface ICustomerService
{
Customer Get(int id);
}
CustomerService.cs
public class CustomerService: ICustomerService
{
private readonly IAuditLog<CustomerService> _auditManager;
public CustomerService(IAuditLog<CustomerService> auditManager)
{
_auditManager = auditManager;
}
public Customer Get(int id)
{
// TODO: simulate getting customer from the database.
var customer = new Customer() { Id = 1, Email = "john@doe.com", Age = 21 };
_auditManager.Log("GET");
return customer;
}
}
Suppose you have added a service called FooService
in your project, then nothing needs to be changed in the DI container, except registration of the FooService.
public class FooService
{
private readonly IAudit<FooService> _audit;
public FooService(IAudit<FooService> audit)
{
_audit = audit;
}
}
Startup.cs
public class Startup
{
public static ServiceProvider Configure()
{
var provider = new ServiceCollection()
.AddSingleton<ICustomerService, CustomerService>()
.AddScoped(typeof(IAuditLog<>), typeof(AuditLogManager<>))
.AddLogging(fs => fs.AddConsole())
.BuildServiceProvider(validateScopes: false);
return provider;
}
}
Main.cs
void Main()
{
var container = Startup.Configure();
var customerService = container.GetService<ICustomerService>();
customerService.Get(1);
}
Replace Audit Log output to a different source
Let’s assume that you want to change the audit log output and store it at MongoDB in the future. Set up a MongodbAuditLog as below.
public class MongodbAuditLog : IAuditLog
{
public string SourceName { get; }
public MongodbAuditLog(string sourceName)
{
SourceName = sourceName;
}
public void Log( string action)
{
Console.WriteLine($"{SourceName} Logging -{action} to MongoDb");
}
}
Change the code below in AuditLogManager
that its.
public class AuditLogManager<T> : IAuditLog<T>
{
private readonly IAuditLog _auditLog;
public AuditLogManager()
{
- _auditLog = new ConsoleAuditLog(typeof(T).Name);
+ _auditLog = new MongodbAuditLog(typeof(T).Name);
}
public void Log( string action)
{
_auditLog.Log(action);
}
}