I am trying to connect the DataContexts of my views to view models from another separated assembly.
Brian Lagunas wrote on his blog something to Getting Started with Prism’s new ViewModelLocator, However, his solution is specially to customize the conventions to allow the ViewModelLocator resolving the view models types.
I have the main project (MyApplication.exe) contains the Bootstrapper, Shell and the views In another separated assembly (MyApplication.Process.dll) i have all the view models.
Basing on Brian's explication, i tried the following solution:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
var viewName = viewType.FullName;
var viewAssemblyName = viewType.Assembly.GetName().Name;
var viewModelNameSuffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = viewName.Replace("Views", "ViewModels") + viewModelNameSuffix;
viewModelName = viewModelName.Replace(viewAssemblyName, viewAssemblyName + ".Process");
var viewModelAssemblyName = viewAssemblyName + ".Process";
var viewModelTypeName = string.Format(
CultureInfo.InvariantCulture,
"{0}, {1}",
viewModelName,
viewModelAssemblyName);
return Type.GetType(viewModelTypeName);
});
}
The solution above works correctly, However, i don't know if this is the best way to do that?
All what i want, is to tell Prism ViewModelLocator in which Assemblies it has to find the view models, i mean the same approach of Caliburn.Micro (Looks for the view models in all registered assemblies).
The solution above will not work if my application support the Prism Modularity if the assembly name doesn't end with the word 'Process' for example?
What do you suggest for me ?
I finally solved my issue by setting custom view model resolver to search for view models in all added assemblies catalogs.
First of all, i try to apply the default prism view model locator convention, if no viewmodel found then, i start applying my custom one.
1- I start by getting all assemblies from the AggregateCatalog.
2- I Get all the non-abstract exported types inherit from Prism BindableBase.
3- I Apply the custom convention delegate to get the expected view model.
In my case, the custom convention is all types having the suffix "ViewModel" and the prefix is the view type name: Example: If the view name is "UsersView" the view model should be "UsersViewModel". If the view name is "Users" the view model should be also "UsersViewModel".
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
// The default prism view model type resolver as Priority
Type viewModelType = this.GetDefaultViewModelTypeFromViewType(viewType);
if (viewModelType != null)
{
return viewModelType;
}
// IF no view model found by the default prism view model resolver
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct() ;
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string ViewModelSuffix = "ViewModel";
var isTypeWithViewModelSuffix = t.Name.EndsWith(ViewModelSuffix);
return (isTypeWithViewModelSuffix)
&& ((viewType.Name.EndsWith("View") && viewType.Name + "Model" == t.Name)
|| (viewType.Name + "ViewModel" == t.Name));
});
var resolvedViewModelType = bindableBases.FirstOrDefault(customConvention);
return resolvedViewModelType;
});
The method * GetDefaultViewModelTypeFromViewType * Is the default prism view model locator, its code is exactly the same as in Bart's answer. I hope this will be helpful for others.
I finally solved the problem by creating a new custom MvvmTypeLocator:
public interface IMvvmTypeLocator
{
#region Public Methods and Operators
Type GetViewModelTypeFromViewType(Type viewType);
Type GetViewTypeFromViewModelType(Type viewModelType);
Type GetViewTypeFromViewName(string viewName);
#endregion
}
The implementation :
public class MvvmTypeLocator: IMvvmTypeLocator
{
private AggregateCatalog AggregateCatalog { get; set; }
public MvvmTypeLocator(AggregateCatalog aggregateCatalog)
{
this.AggregateCatalog = aggregateCatalog;
}
public Type GetViewModelTypeFromViewType(Type sourceType)
{
// The default prism view model type resolver as Priority
Type targetType = this.GetDefaultViewModelTypeFromViewType(sourceType);
if (targetType != null)
{
return targetType;
}
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string TargetTypeSuffix = "ViewModel";
var isTypeWithTargetTypeSuffix = t.Name.EndsWith(TargetTypeSuffix);
return (isTypeWithTargetTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && sourceType.Name + "Model" == t.Name)
|| (sourceType.Name + "ViewModel" == t.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewModelType(Type sourceType)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(IView)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string SourceTypeSuffix = "ViewModel";
var isTypeWithSourceTypeSuffix = t.Name.EndsWith(SourceTypeSuffix);
return (isTypeWithSourceTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && t.Name + "Model" == sourceType.Name)
|| (t.Name + "ViewModel" == sourceType.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewName(string viewName)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && typeof(IView).IsAssignableFrom(t) && t.Name.StartsWith(viewName))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
return t.Name.EndsWith("View");
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
private Type GetDefaultViewModelTypeFromViewType(Type viewType)
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(
CultureInfo.InvariantCulture,
"{0}{1}, {2}",
viewName,
suffix,
viewAssemblyName);
return Type.GetType(viewModelName);
}
}
This custom type locator is using the AggregateCatalog to search the target types in all the assembly catalogs. Of course i create its instance on the Bootstrapper once the Bootstrapper's AggregateCatalog is configured:
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginViewModel).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginView).Assembly));
this.mvvmTypeLocator = new MvvmTypeLocator(this.AggregateCatalog);
}
At the end, i just configure the view model locator in the Bootstrapper like the following:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType => this.mvvmTypeLocator.GetViewModelTypeFromViewType(viewType));
}
Note that the methods GetViewTypeFromViewModelType and GetViewTypeFromViewName are searching all the views implementing the interface named IView. this just an empty interface i use to distinct my views from other classes inside the same assembly. If someone use this mvvmTypeLocator, then he has to create his own interface and implement all the views that should be discoverable by the mvvmTypeLocator.