diff --git a/ReactiveUI.Tests/RxRouting.cs b/ReactiveUI.Tests/RxRouting.cs index ffc92064f2..a39e020051 100644 --- a/ReactiveUI.Tests/RxRouting.cs +++ b/ReactiveUI.Tests/RxRouting.cs @@ -34,6 +34,14 @@ public BazViewModel(IScreen hostScreen) HostScreen = hostScreen; } } + + [ViewResolutionTypeOverride(Type = typeof(BazViewModel))] + public class BazViewModel : BazViewModel + { + public BazViewModel(IScreen hostScreen) : base(hostScreen) + { + } + } } namespace Foobar.Views @@ -80,5 +88,25 @@ public void ResolveExplicitViewType() Assert.True(result is BazView); } } + + [Fact] + public void ResolveViewForGenericViewModelType() + { + var resolver = new ModernDependencyResolver(); + + resolver.InitializeSplat(); + resolver.InitializeReactiveUI(); + resolver.Register(() => new BazView(), typeof(IViewFor)); + + using (resolver.WithResolver()) + { + var fixture = new DefaultViewLocator(); + var vm = new BazViewModel(null); + + var result = fixture.ResolveView(vm); + this.Log().Info(result.GetType().FullName); + Assert.True(result is BazView); + } + } } } diff --git a/ReactiveUI/Interfaces.cs b/ReactiveUI/Interfaces.cs index 177a468fb2..ef7c7e101e 100644 --- a/ReactiveUI/Interfaces.cs +++ b/ReactiveUI/Interfaces.cs @@ -468,6 +468,20 @@ public class ViewContractAttribute : Attribute /// public string Contract { get; set; } } + + /// + /// Allows overriding the type information used for view resolution of a view + /// model. This is useful if your view model is a generic type and you would + /// prefer to resolve your view using an interface. + /// + public class ViewResolutionTypeOverrideAttribute : Attribute + { + /// + /// The type that will be used in view resolution instead of the owning + /// view model type + /// + public Type Type { get; set; } + } } // vim: tw=120 ts=4 sw=4 et : diff --git a/ReactiveUI/ViewLocator.cs b/ReactiveUI/ViewLocator.cs index 7015d2624f..e0e22fcaa8 100644 --- a/ReactiveUI/ViewLocator.cs +++ b/ReactiveUI/ViewLocator.cs @@ -50,21 +50,29 @@ public IViewFor ResolveView(T viewModel, string contract = null) // * IViewFor // * IViewFor (the original behavior in RxUI 3.1) - var attrs = viewModel.GetType().GetTypeInfo().GetCustomAttributes(typeof (ViewContractAttribute), true); + var viewModelType = viewModel.GetType(); + + var attrs = viewModelType.GetTypeInfo().GetCustomAttributes(typeof(ViewContractAttribute), true); if (attrs.Any()) { contract = contract ?? ((ViewContractAttribute) attrs.First()).Contract; } + attrs = viewModelType.GetTypeInfo().GetCustomAttributes(typeof(ViewResolutionTypeOverrideAttribute), true); + + if (attrs.Any()) { + viewModelType = ((ViewResolutionTypeOverrideAttribute)attrs.First()).Type; + } + // IFooBarView that implements IViewFor (or custom ViewModelToViewFunc) - var typeToFind = ViewModelToViewFunc(viewModel.GetType().AssemblyQualifiedName); + var typeToFind = ViewModelToViewFunc(viewModelType.AssemblyQualifiedName); var ret = attemptToResolveView(Reflection.ReallyFindType(typeToFind, false), contract); if (ret != null) return ret; // IViewFor (the original behavior in RxUI 3.1) var viewType = typeof (IViewFor<>); - return attemptToResolveView(viewType.MakeGenericType(viewModel.GetType()), contract); + return attemptToResolveView(viewType.MakeGenericType(viewModelType), contract); } IViewFor attemptToResolveView(Type type, string contract)