Skip to content

Instantly share code, notes, and snippets.

@khellang
Last active January 17, 2020 01:14
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save khellang/c9d39444f713eab04c26dc09d5687196 to your computer and use it in GitHub Desktop.
Save khellang/c9d39444f713eab04c26dc09d5687196 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public class Program
{
public static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddSingleton<INameValidator, NameValidator>();
// Imagine this was added by MVC
services.AddSingleton<IGreeter, Greeter>();
services.Decorate<IGreeter>((inner, provider) => new ValidatingGreeter(inner, provider.GetRequiredService<INameValidator>()));
services.Decorate<IGreeter>(inner => new TransactionGreeter(inner));
services.Decorate<IGreeter>(inner => new ErrorHandlingGreeter(inner));
services.Decorate<IGreeter>(inner => new LoggingGreeter(inner));
var serviceProvider = services.BuildServiceProvider();
var greeter = serviceProvider.GetRequiredService<IGreeter>();
greeter.Greet("Kristian");
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection Decorate<TService>(this IServiceCollection services, Func<TService, IServiceProvider, TService> decorator)
{
var descriptors = services.GetDescriptors<TService>();
foreach (var descriptor in descriptors)
{
services.Replace(descriptor.Decorate(decorator));
}
return services;
}
public static IServiceCollection Decorate<TService>(this IServiceCollection services, Func<TService, TService> decorator)
{
var descriptors = services.GetDescriptors<TService>();
foreach (var descriptor in descriptors)
{
services.Replace(descriptor.Decorate(decorator));
}
return services;
}
private static List<ServiceDescriptor> GetDescriptors<TService>(this IServiceCollection services)
{
var descriptors = new List<ServiceDescriptor>();
foreach (var service in services)
{
if (service.ServiceType == typeof(TService))
{
descriptors.Add(service);
}
}
if (descriptors.Count == 0)
{
throw new InvalidOperationException($"Could not find any registered services for type '{typeof(TService).FullName}'.");
}
return descriptors;
}
private static ServiceDescriptor Decorate<TService>(this ServiceDescriptor descriptor, Func<TService, IServiceProvider, TService> decorator)
{
return descriptor.WithFactory(provider => decorator((TService) descriptor.GetInstance(provider), provider));
}
private static ServiceDescriptor Decorate<TService>(this ServiceDescriptor descriptor, Func<TService, TService> decorator)
{
return descriptor.WithFactory(provider => decorator((TService) descriptor.GetInstance(provider)));
}
private static ServiceDescriptor WithFactory(this ServiceDescriptor descriptor, Func<IServiceProvider, object> factory)
{
return ServiceDescriptor.Describe(descriptor.ServiceType, factory, descriptor.Lifetime);
}
private static object GetInstance(this ServiceDescriptor descriptor, IServiceProvider provider)
{
if (descriptor.ImplementationInstance != null)
{
return descriptor.ImplementationInstance;
}
if (descriptor.ImplementationType != null)
{
return provider.GetServiceOrCreateInstance(descriptor.ImplementationType);
}
return descriptor.ImplementationFactory(provider);
}
private static object GetServiceOrCreateInstance(this IServiceProvider provider, Type type)
{
return ActivatorUtilities.GetServiceOrCreateInstance(provider, type);
}
}
public interface IGreeter
{
void Greet(string name);
}
public class Greeter : IGreeter
{
public void Greet(string name)
{
Console.WriteLine("- GREETER: START");
Console.WriteLine($"- GREETER: Hello {name}!");
// Uncomment this to see the transaction roll back and the error handler kick in...
// throw new Exception("Oops! Something went wrong...");
Console.WriteLine("- GREETER: END");
}
}
public class LoggingGreeter : IGreeter
{
private readonly IGreeter _inner;
public LoggingGreeter(IGreeter inner)
{
_inner = inner;
}
public void Greet(string name)
{
Console.WriteLine("- LOGGING: START");
_inner.Greet(name);
Console.WriteLine("- LOGGING: END");
}
}
public class FakeTransaction : IDisposable
{
private FakeTransaction()
{
}
private bool IsCommitted { get; set; }
public void Commit()
{
IsCommitted = true;
Console.WriteLine("- TRANSACTION: COMMITTED");
}
public void Dispose()
{
if (!IsCommitted)
{
Console.WriteLine("- TRANSACTION: ROLLING BACK");
}
}
public static FakeTransaction Begin()
{
return new FakeTransaction();
}
}
public class TransactionGreeter : IGreeter
{
private readonly IGreeter _inner;
public TransactionGreeter(IGreeter inner)
{
_inner = inner;
}
public void Greet(string name)
{
Console.WriteLine("- TRANSACTION: START");
try
{
using (var transaction = FakeTransaction.Begin())
{
_inner.Greet(name);
transaction.Commit();
}
}
finally
{
Console.WriteLine("- TRANSACTION: END");
}
}
}
public class ErrorHandlingGreeter : IGreeter
{
private readonly IGreeter _inner;
public ErrorHandlingGreeter(IGreeter inner)
{
_inner = inner;
}
public void Greet(string name)
{
Console.WriteLine("- ERROR HANDLER: START");
try
{
_inner.Greet(name);
}
catch (Exception e)
{
Console.WriteLine($"- ERROR HANDLER: {e.Message}");
}
Console.WriteLine("- ERROR HANDLER: END");
}
}
public interface INameValidator
{
bool IsValid(string name);
}
public class NameValidator : INameValidator
{
public bool IsValid(string name)
{
return name.StartsWith("K");
}
}
public class ValidatingGreeter : IGreeter
{
private readonly IGreeter _inner;
private readonly INameValidator _validator;
public ValidatingGreeter(IGreeter inner, INameValidator validator)
{
_inner = inner;
_validator = validator;
}
public void Greet(string name)
{
Console.WriteLine("- VALIDATOR: START");
if (!_validator.IsValid(name))
{
throw new InvalidOperationException("Invalid name.");
}
_inner.Greet(name);
Console.WriteLine("- VALIDATOR: END");
}
}

Output

- LOGGING: START
- ERROR HANDLER: START
- TRANSACTION: START
- VALIDATOR: START
- GREETER: START
- GREETER: Hello Kristian!
- GREETER: END
- VALIDATOR: END
- TRANSACTION: COMMITTED
- TRANSACTION: END
- ERROR HANDLER: END
- LOGGING: END
@eglasius
Copy link

eglasius commented Jul 8, 2016

Is this gist relying on anything from the built in container, or is it using the abstractions in a way that work with other containers?

@khellang
Copy link
Author

khellang commented Sep 7, 2016

@eglasius: Sorry for the late answer, I don't think I'm getting notifications for gists...

Is this gist relying on anything from the built in container, or is it using the abstractions in a way that work with other containers?

This isn't really specific to the default container. It basically registers a factory. If you're using this abstractions with other containers, the container will receive the factory and register it, just like the default container.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment