Xamarin initializing ViewModel prior to PushAsync() - c#

I have a Xamarin App with a login page, I register my DB, Alert, Media, and Navigation Services with ServiceContainer. I then try to navigate to to the Login Page but before it appears on the screen it calls the constructor for every ViewModel and I can't figure out why???
public partial class App : Application
{
INavigationService NavigationService { get; set; }
public App()
{
InitializeComponent();
RegisterRepositories();
RegisterServices();
NavigationService.ReplaceRoot(ServiceContainer.GetInstance<LoginViewModel>(), false);
}
void RegisterServices()
{
ServiceContainer.Register<IAlertService>(() => new AlertService());
ServiceContainer.Register<IMediaService>(() => new MediaService());
NavigationService = new NavigationService();
NavigationService.AutoRegister(typeof(App).Assembly);
ServiceContainer.Register(NavigationService);
}
void RegisterRepositories()
{
ServiceContainer.Register<IUserProfileRepository>(() => new UserProfileRepository());
}
ServiceContainer:
public static class ServiceContainer
{
static readonly Container _container = new Container();
public static void Register<TService, TImplementation>(bool transient = false) where TService : class where TImplementation : class, TService
{
Lifestyle style = transient ? Lifestyle.Transient : Lifestyle.Singleton;
_container.Register<TService, TImplementation>(style);
}
public static void Register<TService>(Func<TService> generator, bool transient = false) where TService : class
{
Lifestyle style = transient ? Lifestyle.Transient : Lifestyle.Singleton;
_container.Register(generator, style);
}
public static void Register(Type serviceType, Type implementationType, bool isTransient = false)
{
if (isTransient)
{
_container.Register(serviceType, implementationType, Lifestyle.Transient);
}
else
{
_container.Register(serviceType, implementationType, Lifestyle.Singleton);
}
}
public static void Register<TService>(TService instance) where TService : class
{
_container.RegisterInstance(instance);
}
public static T GetInstance<T>() where T : class
{
try
{
return _container.GetInstance<T>();
}
catch (ActivationException)
{
return null;
}
}
internal static T GetRequiredInstance<T>() where T : class
{
return GetInstance<T>() ?? throw new InvalidOperationException(
$#"A required dependency injection class is missing ({typeof(T).FullName}).");
}
}
NavigationService:
public interface IViewFor
{
object ViewModel { get; set; }
}
public interface IViewFor<T> : IViewFor where T : BaseViewModel
{
new T ViewModel { get; set; }
}
public class NavigationService : INavigationService
{
INavigation FormsNavigation => Application.Current.MainPage.Navigation;
readonly Dictionary<Type, Type> _viewModelViewDictionary = new Dictionary<Type, Type>();
public void AutoRegister(Assembly asm)
{
// Loop through everything in the assembly that implements IViewFor<T>
foreach (var type in asm.DefinedTypes.Where(dt => !dt.IsAbstract &&
dt.ImplementedInterfaces.Any(ii => ii == typeof(IViewFor))))
{
// Get the IViewFor<T> portion of the type that implements it
var viewForType = type.ImplementedInterfaces.FirstOrDefault(
ii => ii.IsConstructedGenericType &&
ii.GetGenericTypeDefinition() == typeof(IViewFor<>));
// Register it, using the T as the key and the view as the value
Register(viewForType.GenericTypeArguments[0], type.AsType());
ServiceContainer.Register(viewForType.GenericTypeArguments[0], viewForType.GenericTypeArguments[0], true);
}
}
public void Register(Type viewModelType, Type viewType)
{
if (!_viewModelViewDictionary.ContainsKey(viewModelType))
{
_viewModelViewDictionary.Add(viewModelType, viewType);
}
}
public void ReplaceRoot<T>(bool withNavigationEnabled = true) where T : BaseViewModel
{
ReplaceRoot(ServiceContainer.GetInstance<T>(), withNavigationEnabled);
}
public void ReplaceRoot(BaseViewModel viewModel, bool withNavigationEnabled = true)
{
if (InstantiateView(viewModel) is Page view)
{
if (withNavigationEnabled)
{
Application.Current.MainPage = new NavigationPage(view);
}
else
{
Application.Current.MainPage = view;
}
}
}
public Task PopAsync() => FormsNavigation.PopAsync(true);
public Task PopToRootAsync(bool animate) => FormsNavigation.PopToRootAsync(animate);
public Task PushAsync(BaseViewModel viewModel) => FormsNavigation.PushAsync((Page)InstantiateView(viewModel));
IViewFor InstantiateView(BaseViewModel viewModel)
{
var viewModelType = viewModel.GetType();
var viewType = _viewModelViewDictionary[viewModelType];
var view = (IViewFor)Activator.CreateInstance(viewType);
view.ViewModel = viewModel;
return view;
}
}

Related

Calling a Command from a Separate Class File

So I have been at it for days, and for the life of me cannot find any documentation that fits my situation exactly here.
I have essentially set up a custom navigation service and would like to call the command from my ViewModel Class directly from my User Control.
I think I'm on the edge of having it here, but my lack of experience with C# is shooting me in the foot.
Here is the section of code from my Login.xaml.cs in question:
private LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
private void GrantAccess()
{
int userAccess = Int16.Parse(User.Access);
if (userAccess == 1)
{
MessageBox.Show("The bottom man");
}
if (userAccess == 2)
{
MessageBox.Show("The little boss");
}
if (userAccess == 3)
{
MessageBox.Show("The little big boss");
}
if (userAccess == 4)
{
{
_loginViewModel.NavigateMM1Command.Execute(null);
}
}
}
and here is the command I'm trying to reference from the ViewModel:
public class LoginViewModel : BaseViewModel
{
public ICommand NavigateMM1Command { get; }
public LoginViewModel(NavigationStore navigationStore)
{
NavigateMM1Command = new NavigateCommand<MM1ViewModel>(new NavigationService<MM1ViewModel>(navigationStore, () => new MM1ViewModel(navigationStore)));
}
}
Basically I've been going through tutorial after tutorial trying to apply what they teach to what I need and its worked for the most part but now _loginViewModel is throwing a null reference exception and I'm not sure why.
I have tried:
LoginViewModel loginViewModel = new loginViewModel();
but its asking me to pass a navigationStore argument through it and that feels wrong.
Any help here will cure my temporary insanity XD
You're receiving a Null Object Reference because navigationStore is null when LoginViewModel is being constructed.
That is, you have not configured a means to instantiate the type navigationStore when constructing LoginViewModel.
Dependency Injection (DI), or Invocation of Control (IoC) is bit more comprehensive a subject to cover in this answer.
Having said that,
I'll provide code to review here. It represents a means to configure a service provider using a collection of type mappings.
In this complete, ConsoleApp example, we'll explicitly instantiate a ServiceCollection, add Service Types (specifying mapping where application), and Build the ServiceProvider; With that provider, we'll resolve and instantiate Login type using GetService -- instantiating all the types;
The Types are essentially mockups of the types you've specified, but I've modified some aspects (an made up notioned like what your Execute method and usage of NavigationStore was).
DemoNavTypes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleDemo.NavLoginDemo
{
public interface ICommand
{
void Execute(string? userName);
}
public interface INavigationStore {
public bool this[string index] { get;set; }
}
public interface INavigationService {
void GrantAccessToUser(string userName);
}
public interface INavigationViewModel { }
internal class NavigationStore : INavigationStore
{
private Dictionary<string, bool> userAccessDict;
public NavigationStore() {
userAccessDict = new Dictionary<string, bool>();
}
public bool this[string index] {
get => userAccessDict.TryGetValue(index, out var val) && val;
set => userAccessDict[index] = value;
}
}
internal class NavigationService : INavigationService
{
private readonly INavigationStore _navigationStore;
public NavigationService(INavigationStore navigationStore)
{
_navigationStore = navigationStore;
}
public void GrantAccessToUser(string? userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new ArgumentException(nameof(userName));
_navigationStore[userName!] = true;
}
}
internal class NavigationCommand : ICommand
{
private readonly INavigationService _navigationService;
public NavigationCommand(INavigationService navigationService)
{
_navigationService = navigationService;
}
public void Execute(string? userName)
{
if (userName != null)
{
_navigationService.GrantAccessToUser(userName);
}
}
}
internal class User
{
public string? Name { get; set; }
public string Access { get; set; } = "1";
}
public abstract class BaseViewModel
{
internal User User { get; set; } = new User();
protected BaseViewModel() { }
}
internal class LoginViewModel : BaseViewModel, INavigationViewModel
{
private readonly ICommand _command;
public LoginViewModel(ICommand command) : base()
{
_command = command;
}
internal ICommand NavigateMM1Command => _command;
}
internal class Login
{
private User User => _loginViewModel.User;
private readonly LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
internal void SetAccess(int access)
{
SetAccess($"{access}");
}
internal void SetAccess(string access)
{
User.Access = access;
}
internal void SetUserName(string userName) { User.Name = userName; }
internal async Task GrantAccessAsync()
{
await Task.Yield();
int userAccess = Int16.Parse(User.Access);
switch (userAccess)
{
case 1:
Console.WriteLine("The bottom man");
break;
case 2:
Console.WriteLine("The little boss");
break;
case 3:
Console.WriteLine("The little big boss");
break;
case 4:
_loginViewModel.NavigateMM1Command.Execute(User.Name);
break;
default:
throw new NotImplementedException();
}
}
}
}
Program.cs (using Microsoft.Extensions.DependencyInjection)
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Immutable;
using System.ComponentModel.Design;
using System.Linq;
using ConsoleDemo.NavLoginDemo;
internal class Program
{
private static async Task Main(string[] args)
{
var services = new ServiceCollection();
var provder = ConfigureServices(services);
var login = provder.GetService<Login>();
if (login != null)
{
await login.GrantAccessAsync();
login.SetAccess(2);
await login.GrantAccessAsync();
login.SetAccess(3);
await login.GrantAccessAsync();
login.SetUserName("James Bond");
login.SetAccess(4);
await login.GrantAccessAsync();
}
}
private static IServiceProvider ConfigureServices(IServiceCollection services)
{
return services
.AddScoped<INavigationStore, NavigationStore>()
.AddScoped<INavigationService, NavigationService>()
.AddScoped<ICommand, NavigationCommand>()
.AddScoped<LoginViewModel>()
.AddScoped<Login>()
.BuildServiceProvider();
}
}
Note
-- However,
In your application, you'll probably already have a ServiceCollection instance in your Program.cs or Startup.cs file. And the ServiceProvider (or HostProvider) will be managed over by the application; So, you probably won't need to explicitly resolve (or GetService<T>) -- just add the Service Type (mappings) in ServiceCollection. Those parameter types will be instantiated and 'injected' into the constructor of Type that is itself being instantiated.

How can I cause Simple Injector to use different concrete classes for the same interface, but with different classes

I can do this with StructureMap using Constructor Injection. However I cannot find a way to do this with Simple Injector. Here is some code that illustrates this (sorry for the length)
I've looked at the lambda in the Register method, but can't seem to understand how to call a single application wide instance of the container to get the one instance I need.
These are the object graphs I wish to construct:
var bannerTalker =
new LoudMouth(
new ConsoleShouter(), // Implements IConsoleVoicer
new ObnoxiousBannerGenerator());
var plainTalker =
new TimidSpeaker(
new ConsoleWhisperer()); // Implements IConsoleVoicer
Here's the code:
``` c#
public interface IConsoleVoicer
{
void SaySomething(string whatToSay);
}
public class ConsoleWhisperer : IConsoleVoicer
{
public void SaySomething(string whatToSay)
{
Console.WriteLine(whatToSay?.ToLower());
}
}
public class ConsoleShouter : IConsoleVoicer
{
public void SaySomething(string whatToSay)
{
Console.WriteLine(whatToSay?.ToUpper());
}
}
public interface IBannerGenerator
{
string GetBanner();
}
public class ObnoxiousBannerGenerator : IBannerGenerator
{
public string GetBanner()
{
return "OBNOXIOUS";
}
}
public interface IBannerTalker
{
void SayWithBanner(string somethingToSay);
}
public class LoudMouth : IBannerTalker
{
private IConsoleVoicer Voicer { get; set; }
private IBannerGenerator BannerGenerator { get; set; }
public LoudMouth(
IConsoleVoicer concoleVoicer, IBannerGenerator bannerGenerator)
{
Voicer = concoleVoicer;
BannerGenerator = bannerGenerator;
}
public void SayWithBanner(string somethingToSay)
{
Voicer.SaySomething(string.Format("{0}:{1}",
BannerGenerator.GetBanner(), somethingToSay));
}
}
public interface IPlainTalker
{
void SayIt(string somethingToSay);
}
public class TimidSpeaker : IPlainTalker
{
private IConsoleVoicer Voicer { get; set; }
public TimidSpeaker(IConsoleVoicer concoleVoicer)
{
Voicer = concoleVoicer;
}
public void SayIt(string somethingToSay)
{
Voicer.SaySomething(somethingToSay);
}
}
And this is what I've tried:
static void Main(string[] args)
{
var container = new Container();
container.Register<IBannerGenerator, ObnoxiousBannerGenerator>();
container.Register<IPlainTalker, TimidSpeaker>();
container.Register<IBannerTalker, LoudMouth>();
//HERE IS THE DILEMMA! How do I assign
// to IBannerTalker a A LoudMouth contructed with a ConsoleShouter,
// and to IPlainTalkerTalker a A TimidSpeaker contructed with a ConsoleWhisperer
//container.Register<IConsoleVoicer, ConsoleShouter>();
container.Register<IConsoleVoicer, ConsoleWhisperer>();
var bannerTalker = container.GetInstance<IBannerTalker>();
var plainTalker = container.GetInstance<IPlainTalker>();
bannerTalker.SayWithBanner("i am a jerk");
plainTalker.SayIt("people like me");
}
Ric .Net is right in pointing you at the RegisterConditional methods. The following registrations complete your quest:
container.Register<IBannerGenerator, ObnoxiousBannerGenerator>();
container.Register<IPlainTalker, TimidSpeaker>();
container.Register<IBannerTalker, LoudMouth>();
container.RegisterConditional<IConsoleVoicer, ConsoleShouter>(
c => c.Consumer.ImplementationType == typeof(LoudMouth));
container.RegisterConditional<IConsoleVoicer, ConsoleWhisperer>(
c => c.Consumer.ImplementationType == typeof(TimidSpeaker));

using localization service GetAllLanguages from a component composer, incorrect DI?

I have an interface as below, which I use to add a specific language if it does not exist:
public interface IGetLanguagesService
{
void GetLanguages(ILocalizationService localization);
}
public class LanguageService : IGetLanguagesService
{
ILocalizationService _localizationService;
public void GetLanguages(ILocalizationService localization)
{
_localizationService = localization;
var currentLanguages = _localizationService.GetAllLanguages();
bool exists = false;
foreach (var currentLan in currentLanguages)
{
if (currentLan.IsoCode == "es-ES")
{
exists = true;
}
}
if (!exists)
{
AddLanguage(_localizationService);
}
}
public void AddLanguage(ILocalizationService localization)
{
var languageSE = new Language("es-ES") { CultureName = "es-ES", IsMandatory = true };
localization.Save(languageSE);
}
}
I want to use this at start-up so have created a component composer, which on Initialize() I want to call CallGetLanguages() but Im not entirely sure what should be in Initialize(), I think my DI may be wrong?
public class LanguagesComposer : ComponentComposer<LanguagesComponent>
{
public void Compose(Composition composition)
{
composition.Register<IGetLanguagesService>(Lifetime.Singleton);
composition.Register<ILocalizationService>(Lifetime.Singleton);
composition.Components().Append<LanguagesComponent>();
}
}
public class LanguagesComponent : IComponent
{
public void Initialize()
{
???????
}
public void Terminate()
{
throw new NotImplementedException();
}
IGetLanguagesService _getLanguagesService;
ILocalizationService _localization;
public void CallGetLanguages(IGetLanguagesService getLanguages, ILocalizationService localization)
{
_getLanguagesService = getLanguages;
_localization = localization;
_getLanguagesService.GetLanguages(localization);
}
}
You've passed ILocalizationService localization instance to LanguageService twice, pass it to constructor instead and use a constructor injection. The same issue with LanguagesComponent, pass all its dependencies to constructor instead of methods

No default Instance is registered and cannot be automatically determined for type 'EPiServer.Framework.Cache.IRequestCache

I have the following code
[Quartz.DisallowConcurrentExecutionAttribute()]
public class SearchIndexJob : IJob
{
private readonly ILog _Log = null;
private SearchManager _SearchManager;
public SearchIndexJob()
{
_Log = LogManager.GetLogger(GetType());
}
#region IJob Members
public void Execute(IJobExecutionContext context)
{
var container = new StructureMap.Container();
IServiceConfigurationProvider services = new StructureMapConfiguration(container);
var locator = new EPiServer.ServiceLocation.StructureMapServiceLocator(container);
var context2 = new EPiServer.ServiceLocation.ServiceConfigurationContext(HostType.WebApplication, services);
new Mediachase.Commerce.Initialization.CommerceInitialization().ConfigureContainer(context2);
container.Configure(ce =>
{
ce.For<IMarketService>().Use<MarketServiceDatabase>();
ce.For<IMarket>().Use<MarketImpl>();
ce.For<ICurrentMarket>().Singleton().Use<Mediachase.Commerce.Markets.CurrentMarketImpl>();
ce.For<ISynchronizedObjectInstanceCache>().Singleton().Use<EPiServer.Events.RemoteCacheSynchronization>();
ce.For<IObjectInstanceCache>().Use<HttpRuntimeCache>();
//ce.For<ITypeScannerLookup>().Use<FakeTypeScannerLookup>();
ce.For<IWarehouseRepository>().Singleton().Use<Mediachase.Commerce.Inventory.Database.WarehouseRepositoryDatabase>();
ce.For<IChangeNotificationQueueFactory>().Singleton().Use<CommerceChangeQueueFactory>();
ce.For<IPriceService>().Singleton().Use<PriceServiceDatabase>();
ce.For<IPriceDetailService>().Use<PriceDetailDatabase>();
ce.For<IWarehouseInventoryService>().Singleton().Use<WarehouseInventoryProxy>();
ce.For<IInventoryService>().Singleton().Use<InventoryServiceProvider>();
ce.For<IApplicationContext>().Use<FakeAppContext>();
ce.For<CatalogConfiguration>().Use(CatalogConfiguration.Instance);
ce.For<IRequiredMetaFieldCollection>().Singleton().Use<DefaultRequiredMetaFields>();
ce.For<MetaDataContext>().Singleton().Use(() => CatalogContext.MetaDataContext);
//ce.For<EventContext>().HybridHttpOrThreadLocalScoped().Use(eventContext);
ce.For<FrameworkContext>().Use(() => FrameworkContext.Current);
//ce.For<SqlContext>().Use(() => new SqlContext(BusinessFoundationConfigurationSection.Instance.Connection.Database));
ce.For<IChangeNotificationManager>().Singleton().Use<ChangeNotificationManager>();
////ce.For<Mediachase.Commerce.Catalog.ICatalogSystem>().Singleton().Use(() => Mediachase.Commerce.Catalog.CatalogContext.Current);
ce.For<IEventRegistry>().Use<EPiServer.Events.Clients.EventRegistry>();
ce.For<IEventBroker>().Use<FakeEventBroker>();
ce.For<Mediachase.Search.IndexBuilder>().Use<FakeIndexer>();
});
EPiServer.ServiceLocation.ServiceLocator.SetLocator(locator);
string applicationName = context.JobDetail.Description;
if (String.IsNullOrEmpty(applicationName) || applicationName == "all") // index all applications
{
AppDto dto = AppContext.Current.GetApplicationDto();
foreach (AppDto.ApplicationRow row in dto.Application)
{
IndexApplication(row.Name);
}
}
else
{
IndexApplication(applicationName);
}
}
#endregion
void IndexApplication(string applicationName)
{
_Log.Info(String.Format("Creating Search Manager for \"{0}\" Application.", applicationName));
_SearchManager = new SearchManager(applicationName);
_Log.Info("Created Search Manager.");
try
{
_SearchManager.SearchIndexMessage += new SearchIndexHandler(_SearchManager_SearchIndexMessage);
_SearchManager.BuildIndex(true);
}
catch (Exception ex)
{
_Log.Error("Search Manager Failed.", ex);
}
}
void _SearchManager_SearchIndexMessage(object source, SearchIndexEventArgs args)
{
_Log.Info(String.Format("Percent Complete: {0}%, {1}", Convert.ToInt32(args.CompletedPercentage), args.Message));
}
}
public class FakeEventBroker : IEventBroker
{
public bool Enabled { get; set; }
public System.Threading.Tasks.Task RaiseEventAsync(Guid eventId, Object parameter)
{
return null;
}
public event EventHandler<EventReceivedEventArgs> EventReceived;
public event EventHandler<EventMissedEventArgs> EventMissed;
}
public class FakeAppContext : IApplicationContext
{
public bool HasContentModelTypes { get; set; }
public bool DisableVersionSync { get; set; }
}
public class FakeIndexer : Mediachase.Search.IndexBuilder
{
public FakeIndexer() : base("","","")
{
}
}
and I get this error
"No default Instance is registered and cannot be automatically determined for type 'EPiServer.Framework.Cache.IRequestCache"
in this line " _SearchManager.BuildIndex(true);"
Any ideas?
It is hard to tell but I assume you need to register the IRequestCache in your container
I.e.
container.Configure(ce =>
{
ce.For<IMarketService>().Use<MarketServiceDatabase>();
ce.For<IMarket>().Use<MarketImpl>();
ce.For<IRequestCache>().Use<NoRequestCache>(); // or whatever implementation you need
...
}
Schedule Job is trying to Intialize Commerce, Most probably you will require to fix more then IRequestCache including DBContext, See an integration sample here. GIT Integration Sample

Override custom registration conventions in StructureMap 3

I'm using CQRS pattern in my recent project and used Structuremap 3 as my IoC Container, So I defined following conversion to resolve ICommandHandlers for each BaseEntity types:
public class InsertCommandRegistrationConvention
: StructureMap.Graph.IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType = typeof(ICommandHandler<>);
private static readonly Type _openInsertCommandType = typeof(InsertCommandParameter<>);
private static readonly Type _openInsertCommandHandlerType = typeof(InsertCommandHandler<>);
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof(BaseEntity).IsAssignableFrom(type) &&
type.GetInterfaces().Any(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IAggregateRoot<>)))
{
Type closedInsertCommandType = _openInsertCommandType.MakeGenericType(type);
Type closedInsertCommandHandlerType =
_openInsertCommandHandlerType.MakeGenericType(type);
Type insertclosedHandlerInterfaceType =
_openHandlerInterfaceType.MakeGenericType(closedInsertCommandType);
registry.For(insertclosedHandlerInterfaceType)
.Use(closedInsertCommandHandlerType);
}
}
}
and used it in my CompositionRoot:
public static class ApplicationConfiguration
{
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(s =>
{
s.TheCallingAssembly();
s.WithDefaultConventions();
s.Convention<InsertCommandRegistrationConvention>();
});
});
return ObjectFactory.Container;
}
}
so for each my entity it register appropriate InsertCommandHandler for example it register
the InsertCommandHandler<InsertCommandParameter<Order>> for ICommandHandler<ICommandParameter<Order>>
sometimes I need to register custom InsertCommandHandlers for some Entities for example for Product I want to register non-generic InsertProductCustomCommandHandler class for ICommandHandler<ICommandParameter<Product>> instead InsertCommandHandler<InsertCommandParameter<Product>>(in the other word, I want to override the InsertCommendRegistrationConvention).
How could I do this, with Structuremap 3?
You can do this with the method IContainer.Configure() - the Configure() method allows you to add additional configuration to an existing Container or ObjectFactory
I've simplified your abstractions to demonstrate this in action:
public abstract class BaseEntity { }
public interface ICommandHandler<T> { }
public class ClassA : BaseEntity { }
public class ClassB : BaseEntity { }
public class ClassC : BaseEntity { }
public class ClassD : BaseEntity { }
public class InsertCommandHandler<T> : ICommandHandler<T> { }
public class SpecialInsertDCommandHandler : ICommandHandler<ClassD> { }
And InsertCommandRegistrationConvention
public class InsertCommandRegistrationConvention : IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType =
typeof(ICommandHandler<>);
private static readonly Type _openInsertCommandHandlerType =
typeof(InsertCommandHandler<>);
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof(BaseEntity).IsAssignableFrom(type))
{
Type closedInsertCommandHandlerType =
_openInsertCommandHandlerType.MakeGenericType(type);
Type insertclosedHandlerInterfaceType =
_openHandlerInterfaceType.MakeGenericType(type);
registry.For(insertclosedHandlerInterfaceType)
.Use(closedInsertCommandHandlerType);
}
}
}
This test demonstrates that a container configured with InsertCommandRegistrationConvention will return the generic InsertCommandHandler<> for all 4 of the test classes ClassA to ClassD
[Test]
public void Handle_Initialize_RegistersClassesAToDToReturnInsertCommandHandler()
{
var container = ApplicationConfiguration.Initialize();
var resultA = container.GetInstance<ICommandHandler<ClassA>>();
var resultB = container.GetInstance<ICommandHandler<ClassB>>();
var resultC = container.GetInstance<ICommandHandler<ClassC>>();
var resultD = container.GetInstance<ICommandHandler<ClassD>>();
Assert.That(resultA.GetType() == typeof(InsertCommandHandler<ClassA>));
Assert.That(resultB.GetType() == typeof(InsertCommandHandler<ClassB>));
Assert.That(resultC.GetType() == typeof(InsertCommandHandler<ClassC>));
Assert.That(resultD.GetType() == typeof(InsertCommandHandler<ClassD>));
}
And this test shows the Configure method successfully updates the registration for ClassD to return SpecialInsertDCommandHandler instead of InsertCommandHandler<ClassD>
[Test]
public void Handle_Condfigure_OverridesRegistrationForClassD()
{
var container = ApplicationConfiguration.Initialize();
container.Configure(x =>
{
x.For<ICommandHandler<ClassD>>().Use<SpecialInsertDCommandHandler>();
});
var resultD = container.GetInstance<ICommandHandler<ClassD>>();
Assert.That(resultD.GetType() == typeof(SpecialInsertDCommandHandler));
}
You can modify the convention to say something like "If there's a specific command handler, use that. Otherwise, use the InsertCommandHandler."
It would look like this (like qujck, I simplified the classes):
public class InsertCommandRegistrationConvention : IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType = typeof (ICommandHandler<>);
private static readonly Type _openInsertCommandHandlerType = typeof (InsertCommandHandler<>);
private static readonly IList<Type> _customCommandHandlerTypes;
static InsertCommandRegistrationConvention()
{
_customCommandHandlerTypes = _openInsertCommandHandlerType
.Assembly
.ExportedTypes
.Where(x => !x.IsAbstract)
.Where(x => !x.IsGenericType || x.GetGenericTypeDefinition() != typeof (InsertCommandHandler<>))
.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandler<>)))
.ToArray();
}
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof (BaseEntity).IsAssignableFrom(type))
{
var insertclosedHandlerInterfaceType = _openHandlerInterfaceType.MakeGenericType(type);
var closedInsertCommandHandlerType = _openInsertCommandHandlerType.MakeGenericType(type);
// check for any classes that implement ICommandHandler<T> that are not also InsertCommandHandler<T>
var customHandler = _customCommandHandlerTypes.FirstOrDefault(t => t.GetInterfaces().Any(i => i == insertclosedHandlerInterfaceType));
registry.For(insertclosedHandlerInterfaceType)
.Use(customHandler ?? closedInsertCommandHandlerType);
}
}
}
The classes I used:
public abstract class BaseEntity { }
public class Class1 : BaseEntity { }
public class Class2 : BaseEntity { }
public class SpecialClass :BaseEntity { }
public interface ICommandHandler<T> { }
public class InsertCommandHandler<T> : ICommandHandler<T> { }
public class SpecialClassInsertCommandHandler : ICommandHandler<SpecialClass> { }
The tests that pass:
Assert.That(ObjectFactory.GetInstance<ICommandHandler<Class1>>(), Is.InstanceOf<InsertCommandHandler<Class1>>());
Assert.That(ObjectFactory.GetInstance<ICommandHandler<Class2>>(), Is.InstanceOf<InsertCommandHandler<Class2>>());
Assert.That(ObjectFactory.GetInstance<ICommandHandler<SpecialClass>>(), Is.InstanceOf<SpecialClassInsertCommandHandler>());
The advantage is that you modified your convention, rather than avoided using it for certain items. Which keeps everything in one spot if something about your logic has to change.

Categories