I love Caliburn.Micro. It's a great library that adds a ton of ease to developing WP7 and WPF applications. It has treated me exceptionally well. Today, I found an issue.

Ok, maybe not really an issue, per se, but an inescapable scenario. It seems that Caliburn.Micro uses the assembly name when it builds Uris for navigation in WP7. It relies upon your assembly name matching your default namespace (which is very typical).

An exception is thrown when you attempt to navigate to a url using the INavigationService class. The error reported is:

Navigation is only supported to relative URIs that are fragments, or begin with '/', or which contain ';component/'. Parameter name: uri

When you have a View type and you're trying to get the correct Uri to point to it, you have to consider the full namespace of the new View to figure out the full path. In my case, it was "RickySmith.Contag.TagContactView." CM takes the assembly name and removes it from the type's namespace, then replaces periods (.) with forward slashes (/). In almost all cases it's a great match and works perfectly, but there is one flaw: your assembly name must match your namespace. It's using the assembly name kind of a 'base address' for Uris. My assembly name was just "contag." I wanted to create "contag.xap" … I like to keep assembly names simple (call me old fashion). The result was a uri of "RickySmith/Contag/Views/TagContactView.xaml" which was clearly wrong.

The good news is, Caliburn.Micro is INSANELY customizable, so the fix is pretty easy. Put this into your bootstrapper in the Configure method:

1
2
3
4
5
6
7
8
9
10
11
12
ViewLocator.DeterminePackUriFromType = (viewModelType, viewType) =>
{
    var baseNamespace = "RickySmith.Contag";
    var uri = viewType.FullName.Replace(baseNamespace, string.Empty).Replace(".", "/") + ".xaml";

    if (!this.GetType().Assembly.GetAssemblyName().Equals(viewType.Assembly.GetAssemblyName()))
    {
        return "/" + baseNamespace + ";component" + uri;
    }

    return uri;
};

Replace the value of the baseNamespace variable to be whatever your default namespace is. It will now find your view based upon that, instead of the assembly name. It should still locate views in external assemblies.

Update: There is another naming convention you must follow that is related to this: your view's namespace MUST match it's folder structure.