I have two objects that should be registered together, and also share the same lifetime scoping. I would like to provide a registration extension to encapsulate this and retain the registration fluency, but I need some help. Here's the situation I'm in:
public static IRegistrationBuilder<?, ?, ?>
RegisterChannel<T>(this ContainerBuilder builder, Func<IComponentContext, ChannelFactory<T>> #delegate)
{
// channelfactory and sharedchannel should have same lifetime configuration
var channelfactoryreg = builder.Register(c => #delegate(c));
var sharereg = builder.RegisterType<Wcf.SharedChannel<T>>();
// is it possible to combine them and return?
return ???;
}
How do I fill in the blanks so that I can write (e.g.) builder.RegisterTwo().SingleInstance()? Is it possible to directly or indirectly "union" two IRegistrationBuilderTLAR objects, so that configuring the result configures all the underlying registrations, or is there another way to do this?
More generally: is there a primer out there for working with the Autofac internals?
Thanks for your time.
You should use a module to encapsulate registrations that make sense together.
IMHO, you should call .SingleInstance() in both registration. If they are two services they should be configured twice. If they share more commonalities in registration, you could register them with Assembly Scanning.
I find that I make increasing use of the IRegistrationSource interface for grouping together any related registrations. In your case, they would enable you to use the same registration code for an arbitrary set of types:
public class MyRegistrationSource : IRegistrationSource
{
/// <summary>
/// Gets a value indicating whether the registrations provided by this source are 1:1 adapters on top
/// of other components (I.e. like Meta, Func or Owned.)
/// </summary>
public bool IsAdapterForIndividualComponents
{
get
{
return false;
}
}
/// <summary>
/// Retrieve registrations for an unregistered service, to be used
/// by the container.
/// </summary>
/// <param name="service">The service that was requested.</param>
/// <param name="registrationAccessor">A function that will return existing registrations for a service.</param>
/// <returns>
/// Registrations providing the service.
/// </returns>
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (swt != null)
{
// Register requested service types that pass some test or other
if (swt.ServiceType.HasAttribute<SomeAttribute>(true) ||
typeof(SomeType).IsAssignableFrom(swt.ServiceType))
{
var registration = RegistrationBuilder.ForType(swt.ServiceType)
.InstancePerDependency()
.CreateRegistration();
yield return registration;
}
}
}
}
Gosh, I hate answering my own Q, but all suggested alternatives seemed off-point or too complicated. Nick's comment came the closest to engaging, but only succeeded in scaring me off Autofac internals entirely ;). I investigated, but that was overkill for my situation.
I ended up splitting the difference (as implied in my comment above), abandoning the fluent interface, but still allowing for flexible continuation of the registration by accepting the config into the method itself:
public static void RegisterSharedChannel<T>(this ContainerBuilder builder, Func<IComponentContext, ChannelFactory<T>> #delegate,
Action<Autofac.Builder.IRegistrationBuilder<object, Autofac.Builder.IConcreteActivatorData, Autofac.Builder.SingleRegistrationStyle>> config)
{
builder.Register(c => c.Resolve<Wcf.ISharedChannel<T>>().GetChannel()).ExternallyOwned();
//would be really nice to be able to retain the fluency of the interface, but: http://stackoverflow.com/questions/8608415/fluent-configuration-of-multiple-registrations
//this should suffice for now...
var facreg = builder.Register(c => #delegate(c));
var sharereg = builder.RegisterType<Wcf.SharedChannel<T>>().AsImplementedInterfaces();
config(facreg);
config(sharereg);
}
The calling syntax isn't as pretty (e.g.:
builder.RegisterSharedChannel(c => BuildChannelFactory(...), r => r.SingleInstance());
but all (most of?) the flexibility is still there.
Related
Tl;dr: I want an options class that uses non-nullable types for its members with no defaults.
C# 8.0 introduces Nullable Reference Types.
I've found that using nullable reference types with the ASP.Net Options Pattern is rather difficult, incomplete, or that I am missing something. I am experiencing the same issue described in this stack over flow post.
We don't want to make Name nullable as then we need to place traditional null checks everywhere (which is against the purpose of non-nullable reference types)
We can't create a constructor to enforce the MyOptions class to be created with a non-nullable name value as the Configure method construct the options instance for us
We can't use the null-forgiving operator trick (public string name { get; set; } = null!;) as then we can't ensure the Name property is set and we can end up with a null in the Name property where this would not be expected (inside the services)
I want an options class that uses non-nullable types for its members with no defaults. The answers in that post end up using nullable types anyway (which I am trying to avoid) or defaults (which I am also trying to avoid).
The comments about the options validation bring up good points and look promising, but it turns out that the Validate method still needs an options object to validate, which defeats the purpose if you already have to pass the options object into it.
public ValidateOptionsResult Validate(string name, MyOptions options)
// Pointless if MyOptions options is being passed in here
This is pointless because I have determined that the only way to enforce an options class with all non-nullable members and no defaults is to have a constructor. Take the code sample below for example.
namespace SenderServer.Options
{
using System;
using Microsoft.Extensions.Configuration;
/// <summary>
/// Configuration options for json web tokens.
/// </summary>
public class JwtOptions
{
/// <summary>
/// The secret used for signing the tokens.
/// </summary>
public String Secret { get; }
/// <summary>
/// The length of time in minutes tokens should last for.
/// </summary>
public Int32 TokenExpirationInMinutes { get; }
/// <summary>
/// Configuration options for json web tokens.
/// </summary>
/// <param name="secret"> The secret used for signing the tokens.</param>
/// <param name="tokenExpirationInMinutes">The length of time in minutes tokens should last for.</param>
public JwtOptions(String secret, Int32 tokenExpirationInMinutes)
{
Secret = secret;
TokenExpirationInMinutes = tokenExpirationInMinutes;
}
/// <summary>
/// Create a JwtOptions instance from a configuration section.
/// </summary>
/// <param name="jwtConfiguration">The configuration section.</param>
/// <returns>A validated JwtOptions instance.</returns>
public static JwtOptions FromConfiguration(IConfiguration jwtConfiguration)
{
// Validate the secret
String? secret = jwtConfiguration[nameof(Secret)];
if (secret == null)
{
throw new ArgumentNullException(nameof(Secret));
}
// Validate the expiration length
if (!Int32.TryParse(jwtConfiguration[nameof(TokenExpirationInMinutes)], out Int32 tokenExpirationInMinutes))
{
throw new ArgumentNullException(nameof(TokenExpirationInMinutes));
}
if (tokenExpirationInMinutes < 0)
{
throw new ArgumentOutOfRangeException(nameof(TokenExpirationInMinutes));
}
return new JwtOptions(secret, tokenExpirationInMinutes);
}
}
}
So if I need a constructor with the parameters for the class, then I can instantiate it on my own with something like:
// Configure the JWT options
IConfiguration jwtConfiguration = Configuration.GetSection("Authentication:JwtOptions");
JwtOptions jwtOptions = JwtOptions.FromConfiguration(jwtConfiguration); // This performs validation as well
but then where do I put the jwtOptions? None of the services.Configure<JwtOptions>(jwtOptions); and variants just take in an already-instantiated object (or at least none that I've seen). And lastly, even if they did, you can't use a dependency-injected options class that doesn't have a public parameter-less constructor.
public JwtService(IOptions<JwtOptions> jwtOptions)
I want an options class that uses non-nullable types for its members with no defaults.
Then unfortunately, Microsoft.Extensions.Options simply isn’t for you. The way the Options works is by having a configuration pipeline of multiple sources, actions, and validators that all work with the same options object. Since there is no explicit beginning of this pipeline, and any configuration source can be at any position in the pipeline, the construction of the options object is handled by the framework and comes before any of the configuration sources is invoked.
This is strictly necessary in order for Options to allow the different kind of use cases it has: You can configure options from configuration (Microsoft.Extensions.Configuration), you can configure them through configuration actions, you can configure them through services that have additional dependencies, etc. And all of those can run in any order.
So since the construction of the object happens by the framework, there also need to be defaults that the options object gets created with: Usually, these are just the type’s default value but you can also choose different defaults through the object’s constructor.
If you want to enforce that specific parameters have been configured after the pipeline, you can use post-configure actions to enforce a configuration, or options validation to validate the configured options. But since this all runs in the pipeline, you need to have defaults.
So basically, if you need to have non-nullable properties without default values, then you cannot use Options. At least not out of the box. If you want to do this in order to safely reference the options in your services, then there would be a different way to approach this: Instead of injecting IOptions<T>, inject a non-nullable options object T directly. And have that provided through a factory:
services.AddSingleton<MySafeOptions>(sp =>
{
var options = sp.GetService<IOptions<MyUnsafeOptions>>();
return new MySafeOptions(options.Value);
});
services.Configure<MyUnsafeOptions>(Configuration.GetSection("MyOptions"));
Another option building on #poke's answer would be to pass the IConfiguration in to your singleton and use the ConfigurationBinder.Bind directly. If you add the correct attributes you no longer need an options object to pass into your singleton. So with a class like this:
public class JwtConfiguration
{
public JwtConfiguration(IConfiguration configuration)
{
ConfigurationBinder.Bind(configuration, this);
// ensure the fields are not null so that the attributes are not
// a lie
_ = this.Secret ?? throw new ArgumentException(
$"{nameof(this.Secret)} required",
nameof(configuration));
_ = this.TokenExpirationInMinutes ?? throw new ArgumentException(
$"{nameof(this.TokenExpirationInMinutes)} required",
nameof(configuration));
}
[DisallowNull]
[NotNull]
public string? Secret { get; set; }
[DisallowNull]
[NotNull]
public int32? TokenExpirationInMinutes { get; set; }
}
Then to wire it together:
.ConfigureServices(
(context, services) => services
.AddSingleton<JwtConfiguration>(
(service) => new JwtConfiguration(
context.Configuration.GetSection("JwtConfig")))
.AddSingleton<JwtService, JwtService>());
And consume:
public class JwtService
{
public JwtService(JwtConfiguration configuration)
{
I am writing a .Net Core windows service and here is a snippet of code:
internal static class Program
{
public static async Task Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<IntegrationService>();
});
if (isService)
{
await builder.RunAsServiceAsync();
}
else
{
await builder.RunConsoleAsync();
}
}
}
I want to pass some parameters to my service i.e. IntegrationService - how I can send parameters to my service?
Small update on Joelius answer for .Net Core 3
Given an HostedService with this constructor mixing parameters (TimeSpan) and services (ILogger<StatusService>, IHttpClientFactory)
public StatusService(
TimeSpan cachePeriod,
ILogger<StatusService> logger,
IHttpClientFactory clientFactory)
You can in your Startup.cs add it to your HostedService like this :
services.AddHostedService
(serviceProvider =>
new StatusService(
TimeSpan.FromDays(1),
serviceProvider.GetService<ILogger<StatusService>>(),
serviceProvider.GetService<IHttpClientFactory>()));
While the answers above are correct, they do have the downside that you can't use DI in the Services Constructor anymore.
What I did instead was:
class Settings {
public string Url { get; set; }
}
class SomeService : IHostedService {
public SomeService (string instanceId, IOptionsMonitor<Settings> optionsMonitor) {
var settings = optionsMonitor.Get(instanceId);
}
}
services.Configure<Settings>("Instance1", (s) => s.Url = "http://google.com");
services.Configure<Settings>("Instance2", (s) => s.Url = "http://facebook.com");
services.AddSingleton<IHostedService>(x =>
ActivatorUtilities.CreateInstance<SomeService>(x, "Instance1")
);
services.AddSingleton<IHostedService>(x =>
ActivatorUtilities.CreateInstance<SomeService>(x, "Instance2")
);
This creates named settings for each instance and passes the named settings name to the HostedService.
If you want multiple Services with the same Class and different parameters make sure to use AddSingleton instead of AddHostedService as AddHostedService will add only one instance of the same Type which will result in only one instance being started!
What Joelius answered is correct although there is another way of doing this
services.AddSingleton<IHostedService>(provider => new IntegrationService("Test"));
Before .net core 3 you can use a config class which you can inject into the service via DI.
Your config class could look like this:
class IntegrationConfig
{
public int Timeout { get; set; }
public string Name { get; set; }
}
Then you need to add this config to the DI-system:
services.AddSingleton(new IntegrationConfig
{
Timeout = 1234,
Name = "Integration name"
});
In the class IntegrationService you need to add a constructor which takes an object of the config:
public IntegrationService(IntegrationConfig config)
{
// setup with config or simply store config
}
That's basically all you need. It's not the prettiest solution in my opinion and in .net core 3
you can simply use a factory func to add the HostedService but I think something like this is the best choice
if you're on .net core 2.2 or below.
EDIT:
In the comments Kirk Larkin mentions this:
You can emulate the overload. It's just a wrapper around AddTransient(), which of course does support the factory func approach.
For this you might want to look at the current overload which is accessable here:
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services, Func<IServiceProvider, THostedService> implementationFactory)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(implementationFactory));
return services;
}
Note that the last commit that changed this file was on June 3rd and is tagged for preview6 and preview7 of .net core 3. Because I've never heard of TryAddEnumerable and am no microsoft employee, I don't know if you can directly translate that.
Just from looking at the current implementation of AddTransient and going down the rabbit hole a few files more, I sadly can't draw the lines well enough to be able to give you the exact functionality you're currently able to get with .net core 3.
The workaround I gave still works and seems acceptable depending on the situation.
I'm trying to write an ASP.NET Core 2.2 integration test, where the test setup decorates a specific service that would normally be available to the API as a dependency. The decorator would give me some additional powers I'd need in my integration tests to intercept calls to the underlying service, but I can't seem to properly decorate a normal service in ConfigureTestServices, as my current setup will give me:
An exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.DependencyInjection.Abstractions.dll but was not handled in user code
No service for type 'Foo.Web.BarService' has been registered.
To reproduce this, I've just used VS2019 to create a fresh ASP.NET Core 2.2 API Foo.Web project...
// In `Startup.cs`:
services.AddScoped<IBarService, BarService>();
public interface IBarService
{
string GetValue();
}
public class BarService : IBarService
{
public string GetValue() => "Service Value";
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IBarService barService;
public ValuesController(IBarService barService)
{
this.barService = barService;
}
[HttpGet]
public ActionResult<string> Get()
{
return barService.GetValue();
}
}
...and a companion xUnit Foo.Web.Tests project I utilize a WebApplicationfactory<TStartup>...
public class DecoratedBarService : IBarService
{
private readonly IBarService innerService;
public DecoratedBarService(IBarService innerService)
{
this.innerService = innerService;
}
public string GetValue() => $"{innerService.GetValue()} (decorated)";
}
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(di.GetRequiredService<BarService>()));
});
}
}
public class ValuesControllerTests : IClassFixture<IntegrationTestsFixture>
{
private readonly IntegrationTestsFixture fixture;
public ValuesControllerTests(IntegrationTestsFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public async Task Integration_test_uses_decorator()
{
var client = fixture.CreateClient();
var result = await client.GetAsync("/api/values");
var data = await result.Content.ReadAsStringAsync();
result.EnsureSuccessStatusCode();
Assert.Equal("Service Value (decorated)", data);
}
}
The behavior kind of makes sense, or at least I think it does: I suppose that the little factory lambda function (di => new DecoratedBarService(...)) in ConfigureTestServices cannot retrieve the concrete BarService from the di container because it's in the main service collection, not in the test services.
How can I make the default ASP.NET Core DI container provide decorator instances that have the original concrete type as their inner service?
Attempted solution 2:
I've tried the following:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<BarService>()));
});
}
But this surprisingly runs into the same problem.
Attempted solution 3:
Asking for IBarService instead, like this:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<IBarService>()));
});
}
Gives me a different error:
System.InvalidOperationException: 'Cannot resolve scoped service 'Foo.Web.IBarService' from root provider.'
Workaround A:
I can work around the issue in my small repro like this:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(new BarService()));
});
}
But this hurts a lot in my actual application, because BarService doesn't have a simple parameterless constructor: it has a moderately complex dependency graph, so I really would like to resolve instances from the Startup's DI container.
PS. I've tried to make this question fully self-contained, but there's also a clone-and-run rep(r)o for your convenience.
Contrary to popular belief, the decorator pattern is fairly easy to implement using the built-in container.
What we generally want is to overwrite the registration of the regular implementation by the decorated one, making use of the original one as a parameter to the decorator. As a result, asking for an IDependency should lead to a DecoratorImplementation wrapping the OriginalImplementation.
(If we merely want to register the decorator as a different TService than the original, things are even easier.)
public void ConfigureServices(IServiceCollection services)
{
// First add the regular implementation
services.AddSingleton<IDependency, OriginalImplementation>();
// Wouldn't it be nice if we could do this...
services.AddDecorator<IDependency>(
(serviceProvider, decorated) => new DecoratorImplementation(decorated));
// ...or even this?
services.AddDecorator<IDependency, DecoratorImplementation>();
}
The above code works once we add the following extension methods:
public static class DecoratorRegistrationExtensions
{
/// <summary>
/// Registers a <typeparamref name="TService"/> decorator on top of the previous registration of that type.
/// </summary>
/// <param name="decoratorFactory">Constructs a new instance based on the the instance to decorate and the <see cref="IServiceProvider"/>.</param>
/// <param name="lifetime">If no lifetime is provided, the lifetime of the previous registration is used.</param>
public static IServiceCollection AddDecorator<TService>(
this IServiceCollection services,
Func<IServiceProvider, TService, TService> decoratorFactory,
ServiceLifetime? lifetime = null)
where TService : class
{
// By convention, the last registration wins
var previousRegistration = services.LastOrDefault(
descriptor => descriptor.ServiceType == typeof(TService));
if (previousRegistration is null)
throw new InvalidOperationException($"Tried to register a decorator for type {typeof(TService).Name} when no such type was registered.");
// Get a factory to produce the original implementation
var decoratedServiceFactory = previousRegistration.ImplementationFactory;
if (decoratedServiceFactory is null && previousRegistration.ImplementationInstance != null)
decoratedServiceFactory = _ => previousRegistration.ImplementationInstance;
if (decoratedServiceFactory is null && previousRegistration.ImplementationType != null)
decoratedServiceFactory = serviceProvider => ActivatorUtilities.CreateInstance(
serviceProvider, previousRegistration.ImplementationType, Array.Empty<object>());
if (decoratedServiceFactory is null) // Should be impossible
throw new Exception($"Tried to register a decorator for type {typeof(TService).Name}, but the registration being wrapped specified no implementation at all.");
var registration = new ServiceDescriptor(
typeof(TService), CreateDecorator, lifetime ?? previousRegistration.Lifetime);
services.Add(registration);
return services;
// Local function that creates the decorator instance
TService CreateDecorator(IServiceProvider serviceProvider)
{
var decoratedInstance = (TService)decoratedServiceFactory(serviceProvider);
var decorator = decoratorFactory(serviceProvider, decoratedInstance);
return decorator;
}
}
/// <summary>
/// Registers a <typeparamref name="TService"/> decorator on top of the previous registration of that type.
/// </summary>
/// <param name="lifetime">If no lifetime is provided, the lifetime of the previous registration is used.</param>
public static IServiceCollection AddDecorator<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime? lifetime = null)
where TService : class
where TImplementation : TService
{
return AddDecorator<TService>(
services,
(serviceProvider, decoratedInstance) =>
ActivatorUtilities.CreateInstance<TImplementation>(serviceProvider, decoratedInstance),
lifetime);
}
}
This seems like a limitation of the servicesConfiguration.AddXxx method which will first remove the type from the IServiceProvider passed to the lambda.
You can verify this by changing servicesConfiguration.AddScoped<IBarService>(...) to servicesConfiguration.TryAddScoped<IBarService>(...) and you'll see that the original BarService.GetValue is getting called during the test.
Additionally, you can verify this because you can resolve any other service inside the lambda except the one you're about to create/override. This is probably to avoid weird recursive resolve loops which would lead to a stack-overflow.
There's actually a few things here. First, when you register a service with an interface, you can only inject that interface. You are in fact saying: "when you see IBarService inject an instance of BarService". The service collection doesn't know anything about BarService itself, so you cannot inject BarService directly.
Which leads to the second issue. When you add your new DecoratedBarService registration, you now have two registered implementations for IBarService. There's no way for it to know which to actually inject in place of IBarService, so again: failure. Some DI containers have specialized functionality for this type of scenario, allowing you to specify when to inject which, Microsoft.Extensions.DependencyInjection does not. If you truly need this functionality, you can use a more advanced DI container instead, but considering this is only for testing, that would like be a mistake.
Third, you have a bit of a circular dependency here, as DecoratedBarService itself takes a dependency on IBarService. Again, a more advanced DI container can handle this sort of thing; Microsoft.Extensions.DependencyInjection cannot.
Your best bet here is to use an inherited TestStartup class and factor out this dependency registration into a protected virtual method you can override. In your Startup class:
protected virtual void AddBarService(IServiceCollection services)
{
services.AddScoped<IBarService, BarService>();
}
Then, where you were doing the registration, call this method instead:
AddBarService(services);
Next, in your test project create a TestStartup and inherit from your SUT project's Startup. Override this method there:
public class TestStartup : Startup
{
protected override void AddBarService(IServiceCollection services)
{
services.AddScoped(_ => new DecoratedBarService(new BarService()));
}
}
If you need to get dependencies in order to new up any of these classes, then you can use the passed in IServiceProvider instance:
services.AddScoped(p =>
{
var dep = p.GetRequiredService<Dependency>();
return new DecoratedBarService(new BarService(dep));
}
Finally, tell your WebApplicationFactory to use this TestStartup class. This will need to be done via the UseStartup method of the builder, not the generic type param of WebApplicationFactory. That generic type param corresponds to the entry point of the application (i.e. your SUT), not which startup class is actually used.
builder.UseStartup<TestStartup>();
All the other answers were very helpful:
#ChrisPratt clearly explains the underlying problem, and offers a solution where Startup makes the service registration virtual and then overrides that in a TestStartup that is forced upon the IWebHostBuilder
#huysentruitw answers as well that this is a limitation of the underlying default DI container
#KirkLarkin offers a pragmatic solution where you register BarService itself in Startup and then use that to overwrite the IBarService registration completely
And still, I'd like to offer yet another answer.
The other answers helped me find the right terms to Google for. Turns out, there is the "Scrutor" NuGet package which adds the needed decorator support to the default DI container. You can test this solution yourself as it simply requires:
builder.ConfigureTestServices(servicesConfiguration =>
{
// Requires "Scrutor" from NuGet:
servicesConfiguration.Decorate<IBarService, DecoratedBarService>();
});
Mentioned package is open source (MIT), and you can also just adapt only the needed features yourself, thus answering the original question as it stood, without external dependencies or changes to anything except the test project:
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
// The chosen solution here is adapted from the "Scrutor" NuGet package, which
// is MIT licensed, and can be found at: https://github.com/khellang/Scrutor
// This solution might need further adaptation for things like open generics...
var descriptor = servicesConfiguration.Single(s => s.ServiceType == typeof(IBarService));
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(GetInstance<IBarService>(di, descriptor)));
});
}
// Method loosely based on Scrutor, MIT licensed: https://github.com/khellang/Scrutor/blob/68787e28376c640589100f974a5b759444d955b3/src/Scrutor/ServiceCollectionExtensions.Decoration.cs#L319
private static T GetInstance<T>(IServiceProvider provider, ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)
{
return (T)descriptor.ImplementationInstance;
}
if (descriptor.ImplementationType != null)
{
return (T)ActivatorUtilities.CreateInstance(provider, descriptor.ImplementationType);
}
if (descriptor.ImplementationFactory != null)
{
return (T)descriptor.ImplementationFactory(provider);
}
throw new InvalidOperationException($"Could not create instance for {descriptor.ServiceType}");
}
}
There's a simple alternative to this that just requires registering BarService with the DI container and then resolving that when performing the decoration. All it takes is updating ConfigureTestServices to first register BarService and then use the instance of IServiceProvider that's passed into ConfigureTestServices to resolve it. Here's the complete example:
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<BarService>();
servicesConfiguration.AddScoped<IBarService>(di =>
new DecoratedBarService(di.GetRequiredService<BarService>()));
});
Note that this doesn't require any changes to the SUT project. The call to AddScoped<IBarService> here effectively overrides the one provided in the Startup class.
I know that with the Flurl HTTP .NET library I can set a global proxy by using a custom HttpClientFactory, but is there a way to choose a custom proxy for each request?
With many other programming languages, setting a proxy is as easy as setting an option. For example, with Node.js I can do:
const request = require('request');
let opts = { url: 'http://random.org', proxy: 'http://myproxy' };
request(opts, callback);
The ideal way to do that with Flurl would be something like this, which is currently not possible:
await "http://random.org".WithProxy("http://myproxy").GetAsync();
I also know that creating a FlurlClient/HttpClient for every request is not an option, because of the socket exhaustion issue, which I've experienced myself in the past as well.
The scenario for this is when you need to have a pool of proxies that are rotated in some way, so that each HTTP request potentially uses a different proxy URL.
So after some discussion with the Flurl creator (#228 and #374), the solution we'come up with is to use a custom FlurlClient manager class, which would be in charge of creating the required FlurlClients and the linked HttpClient instances. This is needed because each FlurlClient can only use one proxy at a time, for limitations of how the .NET HttpClient is designed.
If you're looking for the actual solution (and code), you can skip to the end of this answer. The following section still helps if you want to understand better.
[UPDATE: I've also built an HTTP client library that takes care of all the stuff below, allowing to set a per-request proxy out of the box. It's called PlainHttp.]
So, the first explored idea was to create a custom FlurlClientFactory that implements the IFlurlClientFactory interface.
The factory keeps a pool of FlurlClients, and when a new request needs to be sent, the factory is invoked with the Url as the input parameter. Some logic is then performed to decide whether the request should go through a proxy or not. The URL could potentially be used as the discriminator for choosing the proxy to use for the particular request. In my case, a random proxy would be chosen for each request, and then a cached FlurlClient would be returned.
In the end, the factory would create:
at most one FlurlClient per proxy URL (which will be then used for all the requests that have to go through that proxy);
a set of clients for "normal" requests.
Some code for this solution can be found here. After registering the custom factory, there would be not much else to do. Standard requests like await "http://random.org".GetAsync(); would be automagically proxied, if the factory decided to do so.
Unfortunately, this solution has a drawback. It turns out that the custom factory is invoked multiple times during the process of building a request with Flurl. According to my experience, it is called at least 3 times. This could lead to issues, because the factory might not return the same FlurlClient for the same input URL.
The solution
The solution is to build a custom FlurlClientManager class, to completely bypass the FlurlClient factory mechanism and keep a custom pool of clients that are provided on demand.
While this solution is specifically built to work with the awesome Flurl library, a very similar thing can be done using the HttpClient class directly.
/// <summary>
/// Static class that manages cached IFlurlClient instances
/// </summary>
public static class FlurlClientManager
{
/// <summary>
/// Cache for the clients
/// </summary>
private static readonly ConcurrentDictionary<string, IFlurlClient> Clients =
new ConcurrentDictionary<string, IFlurlClient>();
/// <summary>
/// Gets a cached client for the host associated to the input URL
/// </summary>
/// <param name="url"><see cref="Url"/> or <see cref="string"/></param>
/// <returns>A cached <see cref="FlurlClient"/> instance for the host</returns>
public static IFlurlClient GetClient(Url url)
{
if (url == null)
{
throw new ArgumentNullException(nameof(url));
}
return PerHostClientFromCache(url);
}
/// <summary>
/// Gets a cached client with a proxy attached to it
/// </summary>
/// <returns>A cached <see cref="FlurlClient"/> instance with a proxy</returns>
public static IFlurlClient GetProxiedClient()
{
string proxyUrl = ChooseProxy();
return ProxiedClientFromCache(proxyUrl);
}
private static string ChooseProxy()
{
// Do something and return a proxy URL
return "http://myproxy";
}
private static IFlurlClient PerHostClientFromCache(Url url)
{
return Clients.AddOrUpdate(
key: url.ToUri().Host,
addValueFactory: u => {
return CreateClient();
},
updateValueFactory: (u, client) => {
return client.IsDisposed ? CreateClient() : client;
}
);
}
private static IFlurlClient ProxiedClientFromCache(string proxyUrl)
{
return Clients.AddOrUpdate(
key: proxyUrl,
addValueFactory: u => {
return CreateProxiedClient(proxyUrl);
},
updateValueFactory: (u, client) => {
return client.IsDisposed ? CreateProxiedClient(proxyUrl) : client;
}
);
}
private static IFlurlClient CreateProxiedClient(string proxyUrl)
{
HttpMessageHandler handler = new SocketsHttpHandler()
{
Proxy = new WebProxy(proxyUrl),
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(10)
};
HttpClient client = new HttpClient(handler);
return new FlurlClient(client);
}
private static IFlurlClient CreateClient()
{
HttpMessageHandler handler = new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10)
};
HttpClient client = new HttpClient(handler);
return new FlurlClient(client);
}
}
This static class keeps a global pool of FlurlClients. As with the previous solution, the pool consists of:
one client per proxy;
one client per host for all the requests that mustn't go through the proxy (this is actually the default factory strategy of Flurl).
In this implementation of the class, the proxy is chosen by the class itself (using whatever policy you want, e.g. round robin or random), but it can be adapted to take a proxy URL as the input. In that case, remember that with this implementation clients are never disposed after they're created, so you might want to think about that.
This implementation also used the new SocketsHttpHandler.PooledConnectionLifetime option, available since .NET Core 2.1, to solve the DNS issues that arise when your HttpClient instances have a long lifetime. On .NET Framework, the ServicePoint.ConnectionLeaseTimeout property should be used instead.
Using the manager class is easy. For normal requests, use:
await FlurlClientManager.GetClient(url).Request(url).GetAsync();
For proxied requests, use:
await FlurlClientManager.GetProxiedClient().Request(url).GetAsync();
So I have a typical three tiered application layered as below
DAL -> Repository -> Business -> Web.UI/API
I have been reading this article about registering dependencies by centralizing them via modules.
The web layer only has a reference to Business which only has a reference to the Repo which only has a reference to the lowest DAL layer. In this topology since the UI/API layer knows nothing about the Repository and has no reference to it, I can't register the modules in the Repository in the UI/API layer. Similarly I can't register the modules present in the DAL in the Business layer. What I want to do is start the registration process in the top most layer which then sets off a cascading effect of registrations in subsequent layers.
Typically what this would look like is each layer exposing a RegisterAllModules method and somehow trigger the RegisterAllModules method from the layer below it. Has something like this been done? Or is there another way to do this? At this point I don't know if I should roll my own logic out as I mentioned here above, since I don't know if there is a documented way to do something like this or not. Thoughts on how to best go forward here is what I am looking for.
Thanks.
Mmmm... I don't know if what follows is a proper response, but I'm going to try to give you the tools for a solution that suits your exact requirementes.
have you looked into json/xml module configuration? You do not need to know the assemblies through cross reference, you just need to know the name of the assemblies in app.config (or web.config). E.g: you can register one module for Repositories in the Repo assembly and one module for Business services in the Business.dll. This completely removes the need of cross-referencing the various assemblies (for Module scanning, you will still need references for method calls, but that is expected anyway). See here for details: http://docs.autofac.org/en/latest/configuration/xml.html#configuring-with-microsoft-configuration
if you want to enforce no call is done from (say) UI to Repo, you can leverage the "Instance Per Matching Lifetime Scope" function (see http://docs.autofac.org/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope). You can use that registration method in order to enforce a Unit-of-work approach. E.g: a Repository can only be resolved in a "repository" LifetimeScope, and only Business components open scopes tagged "repository".
an alternative approach to tagged scopes is in using the "Instance per Owned<>" pattern. In this way, each Business service would require an Owned<Repository>.
Something like:
var builder = new ContainerBuilder();
builder.RegisterType();
builder.RegisterType().InstancePerOwned();
AFAICT, a correct approach would be to register the components through Modules, referenced by the Json/Xml config, and each Module should target specific LifetimeScopes.
When you a class calls the underlying layer, it should open a new LifetimeScope("underlying layer").
I will elaborate further, if you want advice on implementation strategies.
Best,
Alberto Chiesa
Edit:
I didn't knew the "composition root" meaning. Well, thanks for the info!
I favor a SIMPLE configuration file (be it the .config file or a separate .json or .xml file), because I feel that a list of modules to be imported is simpler done through a list than through a class. But this is opinion.
What is not an opinion is that you can import modules from assembly that are not referenced by the "Composition Root" assembly, in a simple and tested way.
So, I would go for Modules for every component registration, but for a textual configuration file for Module registration. YMMV.
Now, let me show you an example of the Unit of Work pattern that I'm using in many live projects.
In our architecture we make heavy use of a Service Layer, which holds responsibility for opening connections to the db and disposing them when finished, etc.
It's a simpler design than what you're after (I prefer shallow other than deep), but the concept is the same.
If you are "out" of the Service Layer (e.g. in an MVC Controller, or in the UI), you need a ServiceHandle in order to access the Service layer. The ServiceHandle is the only class that knows about Autofac and is responsible for service resolution, invocation and disposal.
The access to the Service Layer is done in this way:
non service classes can require only a ServiceHandle
invocation is done through _serviceHandle.Invoke(Func)
Autofac injects the ready to use handles via constructor injection.
This is done through the use of BeginLifetimeScope(tag) method, and registering services (in a module) in this way:
// register every service except for ServiceBase
Builder.RegisterAssemblyTypes(_modelAssemblies)
.Where(t => typeof(IService).IsAssignableFrom(t) && (t != typeof(ServiceBase)))
.InstancePerDependency();
// register generic ServiceHandle
Builder.RegisterGeneric(typeof(ServiceHandle<>))
.AsSelf()
.AsImplementedInterfaces()
.InstancePerDependency();
And registering every shared resource as InstancePerMatchingLifetimeScope("service")
So, an example invocation would be:
... in the constructor:
public YourUiClass(ServiceHandle<MyServiceType> myserviceHandle)
{
this._myserviceHandle = myserviceHandle;
}
... in order to invoke the service:
var result = _myserviceHandle.Invoke(s => s.myServiceMethod(parameter));
This is the ServiceHandle implementation:
/// <summary>
/// Provides a managed interface to access Model Services
/// </summary>
/// <typeparam name="TServiceType">The Type of the parameter to be managed</typeparam>
public class ServiceHandle<TServiceType> : IServiceHandle<TServiceType> where TServiceType : IService
{
static private readonly ILog Log = LogManager.GetLogger(typeof(ServiceHandle<TServiceType>));
private readonly ILifetimeScope _scope;
/// <summary>
/// True if there where Exceptions caught during the last Invoke execution.
/// </summary>
public bool ErrorCaught { get; private set; }
/// <summary>
/// List of the errors caught during execution
/// </summary>
public List<String> ErrorsCaught { get; private set; }
/// <summary>
/// Contains the exception that was thrown during the
/// last Invoke execution.
/// </summary>
public Exception ExceptionCaught { get; private set; }
/// <summary>
/// Default constructor
/// </summary>
/// <param name="scope">The current Autofac scope</param>
public ServiceHandle(ILifetimeScope scope)
{
if (scope == null)
throw new ArgumentNullException("scope");
_scope = scope;
ErrorsCaught = new List<String>();
}
/// <summary>
/// Invoke a method to be performed using a
/// service instance provided by the ServiceHandle
/// </summary>
/// <param name="command">
/// Void returning action to be performed
/// </param>
/// <remarks>
/// The implementation simply wraps the Action into
/// a Func returning an Int32; the returned value
/// will be discarded.
/// </remarks>
public void Invoke(Action<TServiceType> command)
{
Invoke(s =>
{
command(s);
return 0;
});
}
/// <summary>
/// Invoke a method to be performed using a
/// service instance provided by the ServiceHandle
/// </summary>
/// <typeparam name="T">Type of the data to be returned</typeparam>
/// <param name="command">Action to be performed. Returns T.</param>
/// <returns>A generically typed T, returned by the provided function.</returns>
public T Invoke<T>(Func<TServiceType, T> command)
{
ErrorCaught = false;
ErrorsCaught = new List<string>();
ExceptionCaught = null;
T retVal;
try
{
using (var serviceScope = GetServiceScope())
using (var service = serviceScope.Resolve<TServiceType>())
{
try
{
retVal = command(service);
service.CommitSessionScope();
}
catch (RollbackException rollbackEx)
{
retVal = default(T);
if (System.Web.HttpContext.Current != null)
ErrorSignal.FromCurrentContext().Raise(rollbackEx);
Log.InfoFormat(rollbackEx.Message);
ErrorCaught = true;
ErrorsCaught.AddRange(rollbackEx.ErrorMessages);
ExceptionCaught = rollbackEx;
DoRollback(service, rollbackEx.ErrorMessages, rollbackEx);
}
catch (Exception genericEx)
{
if (service != null)
{
DoRollback(service, new List<String>() { genericEx.Message }, genericEx);
}
throw;
}
}
}
catch (Exception ex)
{
if (System.Web.HttpContext.Current != null)
ErrorSignal.FromCurrentContext().Raise(ex);
var msg = (Log.IsDebugEnabled) ?
String.Format("There was an error executing service invocation:\r\n{0}\r\nAt: {1}", ex.Message, ex.StackTrace) :
String.Format("There was an error executing service invocation:\r\n{0}", ex.Message);
ErrorCaught = true;
ErrorsCaught.Add(ex.Message);
ExceptionCaught = ex;
Log.ErrorFormat(msg);
retVal = default(T);
}
return retVal;
}
/// <summary>
/// Performs a rollback on the provided service instance
/// and records exception data for error retrieval.
/// </summary>
/// <param name="service">The Service instance whose session will be rolled back.</param>
/// <param name="errorMessages">A List of error messages.</param>
/// <param name="ex"></param>
private void DoRollback(TServiceType service, List<string> errorMessages, Exception ex)
{
var t = new Task<string>
service.RollbackSessionScope();
}
/// <summary>
/// Creates a Service Scope overriding Session resolution:
/// all the service instances share the same Session object.
/// </summary>
/// <returns></returns>
private ILifetimeScope GetServiceScope()
{
return _scope.BeginLifetimeScope("service");
}
}
Hope it helps!