I am trying to put the code in one place for resolving Keyed services using Autofac.
I have an interface:
public interface IShipManagerService { }
Two implementations:
public class InventoryShipManagerService : IShipManagerService
{
}
public class DemandShipManagerService : IShipManagerService
{
}
I also have a UserProfileService that I some Session information in:
public interface IUserProfileService
{
OrderType OrderingMode {get;set;}
}
public enum OrderType {Inventory, Demand}
Basically a user switches ordering "modes" and I keep that in session.
What I am trying to do is resolve the correct implementation of the IShipManagerService using Keyed registration - but I want to put it in one spot.
I have this:
builder.RegisterType<InventoryShipManagerService>()
.As<IShipManagerService>().Keyed<OrderType>(OrderType.Inventory);
builder.RegisterType<InventoryShipManagerService>()
.As<IShipManagerService>().Keyed<OrderType>(OrderType.Inventory);
Resolving would be using Autofac's recommended IIndex
private readonly IShipManagerService _shipManagerService;
private readlony IUserProfileService _profileService;
public class ShipToController
{
public ShipToController(IUserProfileService profileService, IIndex<OrderType, IShipManagerService> shipManagerList)
{
_profileService = profileService;
_shipManagerService = shipManagerList[_profileService.OrderType];
}
This will work - but I don't want to put this everywhere I use the IShipManagerService (there are other services that will fall into this category) - my controller constructors will get messy real quick.
What I am looking to do is something like this (I have this working)
builder.Register(ctx =>
{
//so I can get the current "Ordering Mode"
var profileService = ctx.Resolve<IUserProfileService>();
//Default to Inventory
IShipManagerService service = (Inventory)Activator.CreateInstance(typeof(InventoryShipManagerService),
ctx.Resolve<IRepository>(),
ctx.Resolve<IShoppingCartService>()) as IShipManagerService;
switch (profileService.OrderingMode)
{
case OrderingMode.Demand:
//if demand is "on"
service = (DemandShipManagerService)Activator.CreateInstance(typeof(DemandShipManagerService),
ctx.Resolve<IRepository>(),
ctx.Resolve<IShoppingCartService>()) as IShipmanagerService;
}
return service;
}
Two things here.
This works and I know that is not what Autofac recommends (using a Service Locator pattern). However - I feel it is cleaner and easier to maintain to put the service resolution code in one spot of my application - versus resolve the correct type in the components using the services.
This - while it works - seems ugly. Is there a way I can use the Keyed service resolution? In other words register both services and have autofac resolve the correct service implementation of ResolveKeyed(OrderType) based on the the resolved ProfileService.OrderingMode?
I am more or less trying to validate my approach here. I'd appreciate a better way if someone has it.
UPDATE
I am trying to use the
IIndex<T,V>
that Autofac recommends.
I feel I am so close - but I get a service not registered exception.
_builder.RegisterType<TShelfShipToManagerService>().Keyed<IShipToManagerService>(OrderType.Shelf);
_builder.RegisterType<TDemandShipToManagerService>().Keyed<IShipToManagerService>(OrderType.Demand);
_builder.Register(ctx =>
{
var profileService = ctx.Resolve<IUserProfileService>();
//The way Autofac recomends
var services = ctx.Resolve<IIndex<OrderType, IShipToManagerService>>();
//I get Component not Registered here??????
return services[profileService.OrderingType];
//this will go away if I can get the code above to work
IShipToManagerService service = Activator.CreateInstance(typeof(TShelfShipToManagerService),
ctx.Resolve<IRepository>(),
ctx.Resolve<IIntegrationService>(),
ctx.Resolve<IShoppingCartService>(),
ctx.Resolve<IUserProfileService>()
) as IShipToManagerService;
switch (profileService.OrderingType)
{
case OrderType.Demand:
service = Activator.CreateInstance(typeof(TDemandShipToManagerService),
ctx.Resolve<IRepository>(),
ctx.Resolve<IIntegrationService>(),
ctx.Resolve<IShoppingCartService>(),
ctx.Resolve<IUserProfileService>()) as IShipToManagerService;
break;
}
return service;
}).As<IShipToManagerService>();
I do not know why this would not work.
Got it figured out.
Turns out I had two things wrong.
I never registered the services with the interface to resolve against.
_builder.RegisterType<TShelfShipToManagerService>()
.Keyed<IShipToManagerService>(OrderType.Shelf)
.As<IShipToManagerService>(); //was missing that part
_builder.RegisterType<TDemandShipToManagerService>()
.Keyed<IShipToManagerService>(OrderType.Demand)
.As<IShipToManagerService>(); //was missing that part
The approach assumed that the Ordering Mode would be available from the UserProfile service. I had enough exception handling in to make sure that their was a default UserProfile service (for the startup and before a user logs in - there is nothing). The problem was I was trying to resolve against a keyed service that had an ordering mode = 0 (since Ordering Mode is a enum and it was not set by the user session, it gets set to zero).
From above I only registered two keyed services OrderType.Shelf and OrderType.Demand - so when
_builder.Register(ctx =>
{
var profileService = ctx.Resolve<IUserProfileService>();
//The way Autofac recomends
var services = ctx.Resolve<IIndex<OrderType, IShipToManagerService>>();
//The first request ends up with an OrderingType == 0
//Since I haven established the session
//I don't have service registered with a key 0
return services[profileService.OrderingType];
}).As<IShipToManagerService>();
So the fix was just to check for OrderingType == 0 - if it is - then use a default setting.
Here I have a AppSettingService that provides a global setting DefaultOrderingMode.
_builder.Register(ctx =>
{ var profileService = ctx.Resolve<IUserProfileService>();
//Check to see if their is a user profile (OrderType = 0)
//if not - then get the default setting
if (profileService.OrderingType == 0)
{
var appSettingService = ctx.Resolve<IApplicationSettingService>();
profileService.OrderingType = appSettingService.GetApplicationSetting(ApplicationSettings.DefaultOrderingMode).ToEnumTypeOf<OrderType>();
}
//The way Autofac recomends
var services = ctx.Resolve<IIndex<OrderType, IShipToManagerService>>();
return services[profileService.OrderingType];
}).As<IShipToManagerService>();
This is what I was trying to achieve.
I have about 10 services that will become "context" dependent based on what a user is doing. This makes managing the resolution of what service is given to the user clean and maintainable.
All of the services resolved will be correct one based on what mode the user is in and I do not need to check for it in the controllers.
I hope someone else can find use of this.