- 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
Last active
June 16, 2024 18:02
-
-
Save khellang/c9d39444f713eab04c26dc09d5687196 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} |
@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
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?