using Autofac Webform with parameter constructor - c#

We have some legacy app assume that we can not change that SiteSettings class because complete project coding thousands of line will disturb. so we want to solve problem using DI. I created POC app here you can see in global asax there is comment //HOW CAN I PASS TenantId HERE so it will be same for this complete httprequest life.
LegacyCode:
public class OrderController
{
public static string CompleteOrder()
{
return SiteSettings.Instance.DefaultTimeZone();
}
}
public class SiteSettings
{
public ITenantSettings TenantSettings { get; set; }
private static SiteSettings _instance;
private SiteSettings() { }
public static SiteSettings Instance => _instance ?? (_instance = new SiteSettings());
public string DefaultTimeZone()
{
return TenantSettings.DefaultTimeZone();
}
}
New Classes for Injection
public interface ITenantSettings
{
string DefaultTimeZone();
}
public class TenantSettings : ITenantSettings
{
private readonly int _tenantId;
public TenantSettings(int tenantId)
{
_tenantId = tenantId;
}
public string DefaultTimeZone()
{
return "USA Time For Tenant ID " + _tenantId.ToString();
}
}
Global ASAX
public class Global : HttpApplication, IContainerProviderAccessor
{
// Provider that holds the application container.
static IContainerProvider _containerProvider;
// Instance property that will be used by Autofac HttpModules
// to resolve and inject dependencies.
public IContainerProvider ContainerProvider => _containerProvider;
protected void Application_Start(object sender, EventArgs e)
{
// Build up your application container and register your dependencies.
var builder = new ContainerBuilder();
builder.RegisterType<TenantSettings>().As<ITenantSettings>().InstancePerRequest();
_containerProvider = new ContainerProvider(builder.Build());
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
int id = 0;
int.TryParse(HttpContext.Current.Request.QueryString["id"], out id);
var cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
var cp = cpa.ContainerProvider;
cp.RequestLifetime.InjectProperties(SiteSettings.Instance);
//HOW CAN I PASS TENANTID HERE so it will be same for this complete httprequest life.
}
}
Default ASPX
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(OrderController.CompleteOrder());
}
}
Error :
None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'CoreLibrary.Tenants.TenantSettings' can be invoked with the available services and parameters:
Cannot resolve parameter 'Int32 tenantId' of constructor 'Void .ctor(Int32)'.

You can use WithParameter, in this instance I would suggest the ResolvedParameter:
builder.RegisterType<TenantSettings>()
.As<ITenantSettings>()
.InstancePerRequest()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(int) && pi.Name == "tenantId",
(pi, ctx) => int.Parse(HttpContext.Current.Request.QueryString["id"])));
In reality you will need something a little more resilient than int.Parse(HttpContext.Current.Request.QueryString["id"]) but this gives you a flavour of a solution
Update
We need to remove the line _instance ?? (_instance = new SiteSettings()) if we are to inject the dependencies. In my example SiteSettings now has a static Initialise method, and this method is the used to construct the value of SiteSettings.Instance.
Currently we are only interested in injecting ITenantSettings and as we want ITenantSettings to have a lesser lifetime scope (per request) than the scope of SiteSettings (singleton) we should inject a delegate (Func<ITenantSettings>).
public class SiteSettings
{
private static SiteSettings _instance;
private Func<ITenantSettings> _tenantSettingsFactory;
private SiteSettings(Func<ITenantSettings> tenantSettingsFactory)
{
_tenantSettingsFactory = tenantSettingsFactory;
}
public static void Initialise(Func<ITenantSettings> tenantSettingsFactory)
{
_instance = new SiteSettings(tenantSettingsFactory);
}
public ITenantSettings TenantSettings { get { return _tenantSettingsFactory(); } }
public static SiteSettings Instance
{
get {
if (_instance == null) throw new InvalidOperationException();
return _instance;
}
}
public string DefaultTimeZone()
{
return TenantSettings.DefaultTimeZone();
}
}
Here's a test that demonstrates what you are asking:
[Fact]
public void Demonstrate_TenantSettingsFactory_AlwaysResolvesCurrentTenantId()
{
int tenantId = 0;
var builder = new ContainerBuilder();
builder.RegisterType<TenantSettings>()
.As<ITenantSettings>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(int) && pi.Name == "tenantId",
(pi, ctx) => tenantId));
var container = builder.Build();
SiteSettings.Initialise(container.Resolve<ITenantSettings>);
tenantId = 1;
Assert.Equal("USA Time For Tenant ID 1", SiteSettings.Instance.DefaultTimeZone());
tenantId = 2;
Assert.Equal("USA Time For Tenant ID 2", SiteSettings.Instance.DefaultTimeZone());
}
Note I removed InstancePerRequest and HttpContext.Current as I am using a unit test project.

Related

Dependency injection with Options pattern in .net 6

In console application with dependency injection with Options pattern I'm trying to load user input. How can I make "factory like" resolving of
IOptions<ArgSettings>
to load input arguments. If required input arguments are not provided (null or not valid) then use default settings from appsettings.json?
public class ArgSettings
{
public string Name { get; set; }
public string Region { get; set; }
}
public interface IService
{
void DoSomthing();
}
public class MyService: IService
{
private readonly ArgSettings _argSettings;
public MyService(IOptions<ArgSettings> cfg)
{
_argSettings = cfg.Value;
}
public void DoSomthing()
{
}
}
public class Program
{
static void Main(string[] args)
{
ArgSettings argsSettings = BindArgsSettings(Environment.GetCommandLineArgs());
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
var configurationRoot = context.Configuration;
services.Configure<ArgSettings>(
configurationRoot.GetSection("Defaults:Args"));
services.AddTransient<IService, MyService>();
}).Build();
}
}
default config section in appsettings.json:
...
"Defaults": {
"Args": {
"Name": "John",
"Region": "USA"
}
}
...
First, when you use IOptions.. you have to use the ".Value"
below code is from : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
Or more generically (less ambiguous):
public class MyCoolClass
{
private readonly MyWhateverSettings _settings;
public MyCoolClass(IOptions<MyWhateverSettings> options)
{
_settings = options.Value;
}
(back to your code)
While I see this code above:
BindArgsSettings(Environment.GetCommandLineArgs());
I don't see what you are doing with the "result of that call".
Ok, here is a
Unmodified Microsoft Sample: (from https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0
)
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
(the above being used as a sanity check)
What I think you want:
/* has the possibility to be null, if the user "passes" on customizing it */
ArgSettings myArgsSettings = BindArgsSettings(Environment.GetCommandLineArgs());
if (null == myArgsSettings)
{
/* user "passed up" doing it manually, so go get the "Defaults" */
myArgsSettings = configurationRoot.GetSection("Defaults:Args");
}
/* now DI inject */
builder.Services.Configure<ArgSettings>(myArgsSettings);
Aka, you are doing something with the result (from 'BindArgsSettings(Environment.GetCommandLineArgs()') .. aka, what has hydrated the 'myArgsSettings' variable. (the first hydration of myArgsSettings that is)
Or below is a "less ambiguous" implementation:
/* has the possibility to be null, if the user "passes" on customizing it */
ArgSettings userProvidedArgsSettings = BindArgsSettings(Environment.GetCommandLineArgs());
ArgSettings fromJsonArgSettings = null;
if (null == userProvidedArgsSettings)
{
/* user "passed up" doing it manually, so go get the "Defaults" */
fromJsonArgSettings = configurationRoot.GetSection("Defaults:Args");
}
if (null == userProvidedArgsSettings && null == fromJsonArgSettings)
{
throw new ArgumentNullException("Guh, Both UserProvider AND fromJson are null.", (Exception) null);
}
/* now DI inject */
if (null != userProvidedArgsSettings)
{
builder.Services.Configure<ArgSettings>(userProvidedArgsSettings);
}
if (null != fromJsonArgSettings)
{
builder.Services.Configure<ArgSettings>(fromJsonArgSettings);
}

WCF Custom OperationConext Lifetimemanager for EF DbContext

container.RegisterType<IDataContextFactory<MyDataContext>, DefaultDataContextFactory<MyDataContext>>(new PerRequestLifetimeManager());
Created a PerRequestLifetimeManager using OperationContext but it does not seem call setValue function at all, it always trys to go to GetValue() function which always retruns null since nothing has been set.
My goal is to create a lifetimeManager for dbconetxt that will give me a new dbContext per method call. transient is not an option since it won;t work for join query.
public class WcfOperationContext : IExtension<OperationContext>
{
private readonly IDictionary<string, object> items;
private WcfOperationContext()
{
items = new Dictionary<string, object>();
}
public IDictionary<string, object> Items
{
get { return items; }
}
public static WcfOperationContext Current
{
get
{
WcfOperationContext context = OperationContext.Current.Extensions.Find<WcfOperationContext>();
if (context == null)
{
context = new WcfOperationContext();
OperationContext.Current.Extensions.Add(context);
}
return context;
}
}
public void Attach(OperationContext owner) { }
public void Detach(OperationContext owner) { }
}
public class PerRequestLifetimeManager : LifetimeManager
{
private string key;
public PerRequestLifetimeManager()
{
key = Guid.NewGuid().ToString();
}
public override object GetValue()
{
if (WcfOperationContext.Current == null)
{
return null;
}
else
{
return WcfOperationContext.Current.Items[key];
}
}
public override void RemoveValue()
{
if (WcfOperationContext.Current != null)
{
WcfOperationContext.Current.Items.Remove(key);
}
}
public override void SetValue(object newValue)
{
if (WcfOperationContext.Current != null)
{
WcfOperationContext.Current.Items.Add(key, newValue);
}
}
}
My solution for this was to use this nuget package: UnityWCF
The Service should be instantiated by Unity and new instance per call.
For this use this settings on the service:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ...
Inject DbContext where you need. And register in Unity like this:
container.RegisterType<DbContext, YourDbContext>(new HierarchicalLifetimeManager(), ...);

Can unity container resolve default type when name is not found?

Is it possible to register same interface twice, where the first resolves to a default implementation and the second has a name and resolves to another type.
Example:
container.RegisterType(typeof(IMyInterface), typeof(MyDefaultImplementation));
container.RegisterType(typeof(IMyInterface), typeof(MySecondImplementation),"Second Implementations name");
So,
Resolve<IMyInterface>("non existing name")
Should resolve MyDefaultImplementation.
If you're OK with using the container you can do an extension method:
public static class UnityExtensions
{
public static T TryResolve<T>(this IUnityContainer container, string name)
{
if (container.IsRegistered<T>(name))
return container.Resolve<T>(name);
return container.Resolve<T>();
}
}
And use it like:
container.RegisterType<IMyInterface, Foo>();
container.RegisterType<IMyInterface, Bar>("bar");
var foo = container.TryResolve<IMyInterface>("non-existing");
// foo will be Foo
var bar = container.TryResolve<IMyInterface>("bar");
// bar will be Bar.
public interface IMyInterface { }
public class Foo : IMyInterface { }
public class Bar : IMyInterface { }
The downside is that you'll need to know when to use the extension and when not to... Otherwise you can build your own BuilderStrategy.
Heavily influensed by:
Unity - loadConfiguration, how to resolve only those configured
Is there TryResolve in Unity?
I'm not familiar enough with unity, but you cat start with creating you own configuration, add it as extension:
public class DefaultRegistrationFallbackConfiguration : UnityContainerExtension
{
protected override void Initialize()
{
this.Context.Registering += this.AppendRemapPolicy;
}
public override void Remove()
{
this.Context.Registering -= this.AppendRemapPolicy;
}
private void AppendRemapPolicy(object sender, RegisterEventArgs e)
{
if (e.Name != null)
return;
if (e.TypeFrom != null && e.TypeTo != null)
this.Context.Policies.SetDefault<IBuildKeyMappingPolicy>(new MapBuildKeyToDefaultPolicy(e.TypeFrom, e.TypeTo));
if (e.LifetimeManager == null)
return;
throw new NotImplementedException("TODO: lifetime management");
}
}
Create your own IBuildKeyMappingPolicy:
public class MapBuildKeyToDefaultPolicy : IBuildKeyMappingPolicy
{
private readonly Type _typeFrom;
private readonly Type _typeTo;
public MapBuildKeyToDefaultPolicy(Type typeFrom, Type typeTo)
{
this._typeFrom = typeFrom;
this._typeTo = typeTo;
}
public NamedTypeBuildKey Map(NamedTypeBuildKey buildKey, IBuilderContext context)
{
if (buildKey.Type == this._typeFrom)
return new NamedTypeBuildKey(this._typeTo);
throw new InvalidOperationException();
}
}
Test classes:
public interface IFoo
{
void Bar();
}
public class FooNamed : IFoo
{
public void Bar()
{
Console.WriteLine("named one");
}
}
public class FooDefault : IFoo
{
public void Bar()
{
Console.WriteLine("default one");
}
}
Test:
public static class Program
{
static void Main(string[] args)
{
var container = new UnityContainer();
// register extension before use container!
container.AddExtension(new DefaultRegistrationFallbackConfiguration());
container.RegisterType(typeof(IFoo), typeof(FooDefault));
container.RegisterType(typeof(IFoo), typeof(FooNamed), "named");
container.Resolve<IFoo>() .Bar(); // default one
container.Resolve<IFoo>("named") .Bar(); // named one
container.Resolve<IFoo>("unknown").Bar(); // default one
}
}
Output:
default one
named one
default one

Autofac Lambda Expression Registeration

I'm using lambda expression in order to register my component like this
var builder = new ContainerBuilder();
builder.RegisterType<EndPointServiceClient>().As<IEndPointService>();
builder.Register(imp => new EndPointImp(imp.Resolve<IEndPointService>())).As<IEndPointImp>();
builder.Register(log => new Login(LoginEnumType.EmployeeLogin, log.Resolve<IEndPointImp>()));
at the application start I resolve and instance of the login class which is a win form class
Login loginWindow = container.Resolve<Login>();
Application.Run(loginWindow);
but for some reason whenever I inspect the value of imp I find it set to null
public Login(LoginEnumType loginType, IEndPointImp imp)
{
_loginType = loginType;
_imp = imp;
InitializeComponent();
}
would anyone have an idea what I'm doing wrong over here?
I'm not entirely sure why your instance is not working. I have used the code from your example and mocked this up with the code below, and it is indeed all working for me. I am resolved an instance of the Login class and imp is injected with an instance of IEndPointImp from the container.
Perhaps there is something from my example code below which you may have missed and might help you?
void Main()
{
DependencyConfig.Configure();
var loginWindow = DependencyConfig.Container.Resolve<Login>();
}
public class DependencyConfig
{
public static IContainer Container { get; private set;}
public static void Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<EndPointServiceClient>().As<IEndPointService>();
builder.Register(imp => new EndPointImp(imp.Resolve<IEndPointService>())).As<IEndPointImp>();
builder.Register(log => new Login(LoginEnumType.EmployeeLogin, log.Resolve<IEndPointImp>()));
Container = builder.Build();
}
}
public enum LoginEnumType
{
EmployeeLogin
}
public interface IEndPointService
{
}
public class EndPointServiceClient : IEndPointService
{
}
public interface IEndPointImp
{
}
public class EndPointImp : IEndPointImp
{
private readonly IEndPointService _endPointService;
public EndPointImp(IEndPointService endPointService)
{
_endPointService = endPointService;
}
}
public class Login
{
private readonly LoginEnumType _loginType;
private readonly IEndPointImp _imp;
public Login(LoginEnumType loginType, IEndPointImp imp)
{
_loginType = loginType;
_imp = imp;
}
}

Constructor dependency injection via unity with parameters from HttpContext

We're using domain to customize how our application behaves. I'll illustrate it on example:
// default behavior
public class CoreService : IService {
public virtual string Hello { get { return "Hello"; } }
public virtual string FavouriteDrink { get { return "Water"; } }
}
// german.site.com
public class GermanService : CoreService {
public override string Hello { get { return "Gutten tag"; } }
public override string FavouriteDrink { get { return "Beer"; } }
}
// usa.site.com
public class UsaService : CoreService {
public override string FavouriteDrink { get { return "Cofee"; } }
}
Services are bootstrapped as follow:
var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");
I use Unity to bootstrap mvc controllers. IE:
public class HomeController : Controller {
private IService m_Service;
// contructor dependency injection magic - this resolves into "CoreService"
public HomeController([Dependency]IService service) {
if (service == null) {
throw new ArgumentNullException("service");
}
m_Service = service;
}
}
Is there a way how to change unity resolution so it'll take domain into account ? Right now I ended up with
public class HomeController : Controller {
private IService m_Service;
// contructor dependency injection magic - a lot less magical
public HomeController() {
m_Service = DomainServiceLocator.Retrieve<IService>();
}
}
Support classes:
public static class DomainServiceLocator {
private static UnityContainerAdapter adapter;
public static T Retrieve<T>() {
string domain = HttpContext.Current.Request.Url.Host;
if (adapter.IsServiceRegistered(typeof(T), domain)) {
return adapter.Resolve<T>(domain);
}
return adapter.Resolve<T>();
}
}
public class QueryableContainerExtension : UnityContainerExtension {
private List<RegisterInstanceEventArgs> registeredInstances = new List<RegisterInstanceEventArgs>();
private List<RegisterEventArgs> registeredTypes = new List<RegisterEventArgs>();
protected override void Initialize() {
this.Context.Registering += (sender, e) => { this.registeredTypes.Add(e); };
this.Context.RegisteringInstance += (sender, e) => { this.registeredInstances.Add(e); };
}
public bool IsServiceRegistered(Type service, string name) {
return registeredTypes.FirstOrDefault(e => e.TypeFrom == service && e.Name == name) != null
|| registeredInstances.FirstOrDefault(e => e.RegisteredType == service && e.Name == name) != null;
}
}
public class UnityContainerAdapter {
private readonly QueryableContainerExtension queryableContainerExtension;
private readonly IUnityContainer unityContainer;
public UnityContainerAdapter()
: this(new UnityContainer()) {
}
public UnityContainerAdapter(IUnityContainer unityContainer) {
this.unityContainer = unityContainer;
// adding extensions to unity container
this.queryableContainerExtension = new QueryableContainerExtension();
unityContainer.AddExtension(this.queryableContainerExtension);
}
public T Resolve<T>(string name) {
return unityContainer.Resolve<T>(name);
}
public T Resolve<T>() {
return unityContainer.Resolve<T>();
}
public bool IsServiceRegistered(Type service, string name) {
return this.queryableContainerExtension.IsServiceRegistered(service, name);
}
}
I like to use an injection factory in these scenarios when resolving something at runtime. Essentially you're resolving your type via the domain name:
So in your composition root you could register like this:
container.RegisterType<Func<string, IService>>
(
new InjectionFactory(c => new Func<string, IService>(name => c.Resolve<IService>(name)))
);
Then in your HomeController you can inject the delegate
public class HomeController
{
private readonly Func<string,IService> _serviceFactory;
public HomeController(Func<string, IService> serviceFactory)
{
if(serviceFactory==null)
throw new ArgumentNullException("serviceFactory");
this._serviceFactory= serviceFactory;
}
public void DoSomethingWithTheService()
{
var domain = this.HttpContext.Uri.Host;
var service = this._serviceFactory(domain);
var greeting = service.Hello;
}
}
```
This is then still unit testable and you have not leaked the DI contain implementation outside of "composition root".
Also.. should CoreService be abstract to avoid direct instantiation of it?
Below is the solution I ended up with - it is based on #Spencer idea. I've created a factory, default implementation to the factory has a reference to DI container itself (IUnityContainer in my case), so it can perform the resolution based on domain once it is asked to. It is also more "modern friendly" since in current generation of ASP.NET (ASP.NET CORE) there is no such thing as magic singleton providing current HttpContext and DI is hard coded into the framework.
public interface IFactory<T>
{
T Retrieve(string domain);
}
internal sealed class Factory<T> : IFactory<T>
{
private readonly IUnityContainer _container;
public Factory(IUnityContainer container)
{
_container = container;
}
public T Resolve(string domain)
{
// this is actually more complex - we have chain inheritance here
// for simplicity assume service is either registered for given
// domain or it throws an error
return _container.Resolve<T>(domain);
}
}
// bootstrapper
var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");
container.RegisterInstance<IFactory<IService>>(new Factory<IService>(container));
And the home controller looks like
public class HomeController : Controller {
private IFactory<IService> m_Factory;
public HomeController(IFactory<IService> factory) {
m_Factory = factory;
}
private void FooBar() {
var service = m_Factory.Retrieve(this.HttpContext.Uri.Host);
var hello = service.Hello;
}
}
Its also a worth mentioning that - as I'm lazy - I've build a system of decorative attributes like
[Domain("german.site.com")]
public class GermanService : IService { ... }
[DomainRoot]
public class CoreService : IService { ... }
[Domain("usa.site.com")]
public class UsaService : CoreService { ... }
So the bootstrapping is done automatically across all types in given assembly. But that part is a bit lengthy - if anyone is interested I can post it on github.

Categories

Resources