John Papa and Glen Block have recently posted on a topic that touched home for me. They put together a solution to find view models for a given view in a view-first fashion to support Blendability and a clean MVVM presentation pattern. Funny, I put this very pattern together last week after being motivated by what I saw from Rob Eisenberg and Laurent Bugnion at MIX the week before. I too came to the conclusion that it makes a lot of sense to go with a View-first approach if you’re going to make Blend a first class citizen in your development process (as I believe you should).
What John and Glen put together is MEF based (obviously), but what I put together is IoC based. In my case I’m using Unity, but it could be any IoC container. With a very simple convention, I can greatly simplify this entire pattern. That convention is this: The View and the ViewModel will always reside in the same namespaces within the same assembly and the View will always be suffixed with xxxxxView and the ViewModel will always be suffixed with xxxxxViewModel. That’s it. With that convention in place, one line of markup can be used in all views to auto-locate, instantiate, and bind to it’s corresponding ViewModel. No need to Export anything with MEF and no need to have string names on your exports to find by. I like my approach a lot better and my team has been very happy with it so far. It’s very simple, straightforward and requires very little ceremony code.
Below is what the code looks like…
All views have this markup at the root element:
DataContext="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LocatorConverter} }"
This is simply binding the View’s DataContext to itself and passing itself to the LocatorConverter. The LocatorConverter looks like this:
public class ViewModelLocator
{
public static object Locate(object view)
{
try
{
Check.ForNullArg(view, "view");
Type viewType = view.GetType();
string viewTypeName = viewType.Name;
Logger.WriteInfo("Attempting to auto-resolve the view model from the view (type: {0})", viewTypeName);
if (string.IsNullOrEmpty(viewTypeName) || !viewTypeName.EndsWith("View"))
{
throw new PresentationModelException("Attempting to locate a view model instance for an invalid {0} implementation - expected the type name to be suffixed with 'View'", typeof(IView).Name);
}
string viewModelTypeName = string.Concat(viewTypeName, "Model");
var viewModelTypeFullName = viewType.AssemblyQualifiedName.Replace(viewTypeName, viewModelTypeName);
var viewModelType = Type.GetType(viewModelTypeFullName);
if (viewModelType == null)
{
throw new PresentationModelException("Could not find view model type '{0}' in namespace '{1}' within assembly '{2}'. Auto resolution of the view model requires both the view and the view model to reside within the same namespace.", viewModelTypeName, viewType.Namespace, viewType.Assembly.FullName);
}
object resolved = IoC.Resolve(viewModelType);
if (resolved == null)
{
throw new PresentationModelException("Failed to resolve the view model type '{0}'", viewModelTypeFullName);
}
Logger.WriteInfo("Successfully auto-resolved view model (type {0})", viewModelTypeFullName);
return resolved;
}
catch (Exception ex)
{
string viewType = view != null ? view.GetType().FullName : "NULL";
throw new PresentationModelException(ex, "The attempt to auto-instantiate the view model for the view '{0}' threw an exception: {1}. See the inner exception for details.", viewType, ex.Message);
}
}
IoC is a very think shim around Unity that’s setup by our application’s Bootstrapping process:
public static object Resolve(Type type)
{
return _container.Resolve(type);
}
I love MEF, but this doesn’t feel like a good fit for MEF to me – at least not in it’s current form.