To implement this behavior, we just have to replace the public static Func property LocateForModelType of the ViewLocator class.

In order to let the source files of CM intact, I will create a new static class ViewLocatorConfigurator, with a Configure method, which will "override" the default behavior of the LocateForModelType method. And to activate this custom behavior, I will just have to call this method in the Configure method of the BootStrapper.

But before I show you this class, let's create the attribute which will be used:

[DebuggerStepThrough]
[AttributeUsage(AttributeTargets.Class)]
public class UseViewAttribute : Attribute
{
    private readonly string viewName;

    public UseViewAttribute(string viewName)
    {
        Contract.Requires(!string.IsNullOrEmpty(viewName));
        Contract.Ensures(ViewName != null);

        this.viewName = viewName;
    }

    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(ViewName != null);
    }

    public string ViewName { [DebuggerStepThrough] get { return viewName; } }
}

Nothing fancy, we just have to specify the name of the view.

Now, let's see how to use this attribute in our new definition of LocateForModelType:

public static class ViewLocatorConfigurator
{
    public static void Configure()
    {
        ViewLocator.LocateForModelType = (modelType, displayLocation, context) =>
                                {
                                    var useViewAttributes = modelType.GetCustomAttributes(typeof(UseViewAttribute), true)
                                        .Cast<UseViewAttribute>();
                                        
                                    Contract.Assert(useViewAttributes.Count() <= 1, "There can only be zero or one UseViewAttribute on a view model");

                                    string viewTypeName;
                                        
                                    if (useViewAttributes.Count() == 1)
                                        viewTypeName = string.Concat(modelType.Namespace.Replace("Model", string.Empty), ".", useViewAttributes.First().ViewName);
                                    else
                                    {
                                        viewTypeName = modelType.FullName.Replace("Model", string.Empty);
                                        if (context != null)
                                        {
                                            viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
                                            viewTypeName = viewTypeName + "." + context;
                                        }
                                    }

                                    var viewType = (from assembly in AssemblySource.Instance
                                                    from type in assembly.GetExportedTypes()
                                                    where type.FullName == viewTypeName
                                                    select type).FirstOrDefault();

                                    return viewType == null
                                                ? new TextBlock {Text = string.Format("{0} not found.", viewTypeName)}
                                                : ViewLocator.GetOrCreateViewType(viewType);
                                };
    }
}

I don't think this needs a lot of explanations. I just check there is only zero or one UseViewAttribute on top of the VM, and I create the full name of the view, based on the namespace of the VM and the name of the view given in parameter to the attribute. And I use the same code as the original version to create the view.

This was a short article, but I hope you enjoyed it, and that it will convince you of the power of Caliburn Micro.

See you soon!