ViewLocator Issues..

Topics: Bootstrappers & IoC, Conventions
Aug 23, 2012 at 7:26 PM
Edited Aug 23, 2012 at 9:22 PM

Well lets say i have 2 assemblies

Core and Core.Gui

Core contains a namespace called Core.Config this namespace has a class called Core.Config.DiskSource.

In Core.Gui we have a view called Core.Gui.Views.DiskSourceView.

How do i tell the ViewLocator to map those 2 together?

I have tried the following with no success: 

ViewLocator.AddNamespaceMapping("Core.Config", "Core.Gui.ViewModels");
ViewLocator.AddNamespaceMapping("Core.Config", "Core.Gui.Views"); ViewLocator.AddNamespaceMapping("Core.Config", "Core.Gui");
Aug 23, 2012 at 7:58 PM
Edited Aug 23, 2012 at 7:59 PM

Have you overridden SelectAssemblies method in your Bootstrapper class?
You have to include both assemblies there.

Otherwise only the assembly containing the Application class is inspected by the ViewLocator.

Aug 23, 2012 at 8:08 PM
Edited Aug 23, 2012 at 8:17 PM

Yes i have the following code in my Bootstrapper (its located in the gui assembly)

    protected override IEnumerable<System.Reflection.Assembly> SelectAssemblies()
    {
        yield return Assembly.GetExecutingAssembly();
        yield return Assembly.GetAssembly(typeof(Core.Config.DiskSource));
        yield break;
    }

this returns a IEnumerable contaning both assemlies

My guess is that it gets problem's a because it does not understand when a class ends with Source instead of ViewModel but thats just a theory...

Aug 23, 2012 at 8:47 PM

Your guess is right. There is no convention for classes ending with Source.

The following discussion might help you:
http://caliburnmicro.codeplex.com/discussions/360966

Aug 23, 2012 at 8:51 PM

Im not sure what part of the discussion you want me to look at, my guess was the

var config = new TypeMappingConfiguration()
{   
   ViewModelSuffix = "ViewModelDesign",
};

the problem with that is that in my normal Gui assembly the ViewModels are named just that "...ViewModel".

So if i change the ViewModelSuffix i loose the binding of my normal ViewModels

Aug 23, 2012 at 9:39 PM

After tinkering around like a crazy person i think i found a solution, but i don't know its a good solution:
ViewLocator.NameTransformer.AddRule("^Core.Config.(.*?)Source$""Core.Gui.Views.$1SourceView");

 

Sep 15, 2012 at 2:49 PM

@Petoj87: Typically, having CM support custom conventions like this is handled through the TypeMappingConfiguration class. However, your particular convention uses a blank ViewModel suffix (i.e. "DiskSource" rather than "DiskSourceViewModel"). While the TypeMappingConfiguration allows you to change what that suffix is, the current stable build of the framework performs validation on the configuration settings and disallows blank values. I removed the validation and tested it, and it appears to work fine without breaking anything. I committed the changes (http://caliburnmicro.codeplex.com/SourceControl/changeset/40cc11e10a6c), which you can manually apply to your local copy of the source code or pull from the repository until the next release.

As for actually using TypeMappingConfiguration for your use case, refer to this sample code below:

var config = new TypeMappingConfiguration()
{
    DefaultSubNamespaceForViews = "Views",
    DefaultSubNamespaceForViewModels = "Config",
    ViewModelSuffix = "",                                                       //Convention has no ViewModel suffix
    NameFormat = "{0}{1}",
    ViewSuffixList = new List<string>(new[] { "View", "Page" }),                //Support View names that end with "View" or "Page"
    UseNameSuffixesInMappings = true,
    IncludeViewSuffixInViewModelNames = false
};

ViewLocator.ConfigureTypeMappings(config);
ViewModelLocator.ConfigureTypeMappings(config);

ViewLocator.AddNamespaceMapping("Core.Config", "Core.Gui.Views", "View");       //3rd argument is optional. ViewSuffix = "View" by default
ViewLocator.AddNamespaceMapping("Core.Config", "Core.Gui.Views", "Page");       //Can omit if "Page" isn't used as a suffix
ViewModelLocator.AddNamespaceMapping("Core.Gui.Views", "Core.Config", "View");  //3rd argument is optional. ViewSuffix = "View" by default
ViewModelLocator.AddNamespaceMapping("Core.Gui.Views", "Core.Config", "Page");  //Can omit if "Page" isn't used as a suffix
Sep 15, 2012 at 2:56 PM
Edited Sep 15, 2012 at 2:56 PM

Here's some sample code using the configuration above:

var typename = "Core.Config.DiskSource";
var result = String.Join(", ", ViewLocator.TransformName(typename, null).ToArray());
//Output: Core.Gui.Views.DiskSourcePage, Core.Gui.Views.DiskSourceView, Core.Views.DiskSourcePage, 
//Core.Config.DiskSourcePage, Core.Views.DiskSourceView, Core.Config.DiskSourceView

result = String.Join(", ", ViewLocator.TransformName(typename, "Tabs").ToArray());
//Output: Core.Gui.Views.DiskSource.Tabs, Core.Gui.Views.DiskSource.Tabs, Core.Views.DiskSource.Tabs, 
//Core.Config.DiskSource.Tabs, Core.Views.DiskSource.Tabs, Core.Config.DiskSource.Tabs

typename = "Core.Gui.Views.DiskSourceView";
result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
//Output: Core.Config.IDiskSource, Core.Config.IDiskSource, Core.Config.DiskSource, Core.Config.DiskSource, 
//Core.Gui.Config.IDiskSource, Core.Gui.Config.IDiskSource, Core.Gui.Config.DiskSource, Core.Gui.Config.DiskSource, 
//Core.Gui.Views.IDiskSource, Core.Gui.Views.IDiskSource, Core.Gui.Views.DiskSource, Core.Gui.Views.DiskSource

result = String.Join(", ", ViewModelLocator.TransformName(typename, false).ToArray());
//Output: Core.Config.DiskSource, Core.Config.DiskSource, Core.Gui.Config.DiskSource, Core.Gui.Config.DiskSource, 
//Core.Gui.Views, Core.Gui.Views, Core.Gui.Views.DiskSource, Core.Gui.Views.DiskSource

typename = "Core.Gui.Views.DiskSourcePage";
result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
//Output: Core.Config.IDiskSource, Core.Config.IDiskSource, Core.Config.DiskSource, Core.Config.DiskSource, 
//Core.Gui.Config.IDiskSource, Core.Gui.Config.IDiskSource, Core.Gui.Config.DiskSource, Core.Gui.Config.DiskSource, 
//Core.Gui.Views.IDiskSource, Core.Gui.Views.IDiskSource, Core.Gui.Views.DiskSource, Core.Gui.Views.DiskSource

result = String.Join(", ", ViewModelLocator.TransformName(typename, false).ToArray());
//Output: Core.Config.DiskSource, Core.Config.DiskSource, Core.Gui.Config.DiskSource, Core.Gui.Config.DiskSource, 
//Core.Gui.Views, Core.Gui.Views, Core.Gui.Views.DiskSource, Core.Gui.Views.DiskSource
Oct 9, 2013 at 3:52 PM
I'm trying to follow the guidance on customizing configurations, but not having any luck.
I have my View in a View folder, with namespace to match: Mvvm.Cal.View, class name AppView
The ViewModel is in a ViewModel folder, namespace: Mvvm.Cal.ViewModel, class name AppViewModel
In the constructor for the Bootstrapper, I have:
       var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View"

        };

        ViewLocator.ConfigureTypeMappings(config);
        ViewLocator.AddNamespaceMapping("", ".View");
In the constructor for the ViewModel, this code:
       var typename = this.GetType().ToString();
       var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToAray());
shows result as an empty string.

I must be doing something basic, simple wrong.
Any ideas?
Oct 9, 2013 at 5:20 PM
Edited Oct 9, 2013 at 5:22 PM
rspiewak wrote:
I'm trying to follow the guidance on customizing configurations, but not having any luck.
I have my View in a View folder, with namespace to match: Mvvm.Cal.View, class name AppView
The ViewModel is in a ViewModel folder, namespace: Mvvm.Cal.ViewModel, class name AppViewModel
In the constructor for the Bootstrapper, I have:
       var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View"

        };

        ViewLocator.ConfigureTypeMappings(config);
        ViewLocator.AddNamespaceMapping("", ".View");
In the constructor for the ViewModel, this code:
       var typename = this.GetType().ToString();
       var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToAray());
shows result as an empty string.

I must be doing something basic, simple wrong.
Any ideas?
The main issue is that your code configured the ViewLocator while calling ViewModelLocator.TransformName(). The second issue is that you need to specify DefaultSubNamespaceForViewModels = "ViewModel" on the configuration object too. IOW, override both DefaultSubNamespaceForViewModels and DefaultSubNamespaceForViews and then call ConfigureTypeMappings() on both the ViewLocator and ViewModelLocator. You WILL NOT need to manually call ViewLocator.AddNamespaceMapping() after that.

It appears that the only thing you're doing is changing the "subnamespaces" from the default convention (i.e. "View" instead of "Views" and "ViewModel" instead of "ViewModels"). You are using the default type naming convention (i.e. SomethingSomethingView and SomethingSomethingViewModel). This use case is in the documentation. Look for the example called "Override the default subnamespaces". It's the first example, in fact.
Oct 9, 2013 at 6:22 PM
But I tried that, same outcome:
   public AppBootstrapper()
    {
        var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View",
            DefaultSubNamespaceForViewModels = "ViewModel"

        };

        ViewLocator.ConfigureTypeMappings(config);

    }
I read the documentation, and have been trying to follow it. I re-compiled the Caliburn.Micro code so I could debug into it, but it's hard to figure it out when it at runtime you see an array of lambdas and regexes.
Oct 9, 2013 at 6:58 PM
Edited Oct 9, 2013 at 7:00 PM
rspiewak wrote:
But I tried that, same outcome:
   public AppBootstrapper()
    {
        var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View",
            DefaultSubNamespaceForViewModels = "ViewModel"

        };

        ViewLocator.ConfigureTypeMappings(config);

    }
I read the documentation, and have been trying to follow it. I re-compiled the Caliburn.Micro code so I could debug into it, but it's hard to figure it out when it at runtime you see an array of lambdas and regexes.
No, you didn't try what I suggested in my last post:
IOW, override both DefaultSubNamespaceForViewModels and DefaultSubNamespaceForViews and then call ConfigureTypeMappings() on BOTH the ViewLocator and ViewModelLocator.
And take a closer look at the example in the documentation that I referred to as well:
//Override the default subnamespaces
var config = new TypeMappingConfiguration
{
    DefaultSubNamespaceForViewModels = "MyViewModels",
    DefaultSubNamespaceForViews = "MyViews"
};
 
ViewLocator.ConfigureTypeMappings(config);
ViewModelLocator.ConfigureTypeMappings(config); //<--------YOU ARE MISSING THIS
How do you expect this to work:
var typename = this.GetType().ToString();
var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
if you're only configuring the ViewLocator in your bootstrapper?
Oct 10, 2013 at 1:22 PM
True. But it didn't help:
    public AppBootstrapper()
    {
        var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View",
            DefaultSubNamespaceForViewModels = "ViewModel"

        };

        ViewLocator.ConfigureTypeMappings(config);
        ViewModelLocator.ConfigureTypeMappings(config);

    }
produces the same (lack of) result.
What else am I missing here?
Thanks.
Oct 10, 2013 at 2:21 PM
Edited Oct 10, 2013 at 2:32 PM
    public AppBootstrapper()
    {
        var config = new TypeMappingConfiguration
        {
            DefaultSubNamespaceForViews = "View",
            DefaultSubNamespaceForViewModels = "ViewModel"

        };

        ViewLocator.ConfigureTypeMappings(config);
        ViewModelLocator.ConfigureTypeMappings(config);
        var typename = "Mvvm.Cal.View.AppView";
        var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());  
    }
Add those two lines and put a break point on that last line. Tell me what value you're getting for result.

Also, try changing your code to this:
var typename = this.GetType().FullName;          //<---------CHANGE TO THIS
var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
Look at the source code for the method "LocateTypeForViewType" in ViewModelLocator.cs and see how TransformName is being called.
Oct 10, 2013 at 3:37 PM
I took the first part of your suggestion, and it looks like it worked (bold is the one I want):
result: "Mvvm.Cal.ViewModel.IAppViewModel, Mvvm.Cal.ViewModel.IApp, Mvvm.Cal.ViewModel.AppViewModel, Mvvm.Cal.ViewModel.App, Mvvm.Cal.View.IAppViewModel, Mvvm.Cal.View.IApp, Mvvm.Cal.View.AppViewModel, Mvvm.Cal.View.App"

However, when I get to the breakpoint in AppViewModel, it's gone!
   public AppViewModel()
   {
        var typename = this.GetType().ToString();
        var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
   }
In either case, typename is "Mvvm.Cal.ViewModel.AppViewModel"
Apparently, the TYpeMappingConfiguration change made in the bootstrapper isn't still there when I get to the model?
Thanks!
Oct 10, 2013 at 3:38 PM
It doesn't matter whether I use FullName or ToString, they're both the same.
Oct 10, 2013 at 4:19 PM
rspiewak wrote:
I took the first part of your suggestion, and it looks like it worked (bold is the one I want):
result: "Mvvm.Cal.ViewModel.IAppViewModel, Mvvm.Cal.ViewModel.IApp, Mvvm.Cal.ViewModel.AppViewModel, Mvvm.Cal.ViewModel.App, Mvvm.Cal.View.IAppViewModel, Mvvm.Cal.View.IApp, Mvvm.Cal.View.AppViewModel, Mvvm.Cal.View.App"

However, when I get to the breakpoint in AppViewModel, it's gone!
   public AppViewModel()
   {
        var typename = this.GetType().ToString();
        var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());
   }
In either case, typename is "Mvvm.Cal.ViewModel.AppViewModel"
Apparently, the TYpeMappingConfiguration change made in the bootstrapper isn't still there when I get to the model?
Thanks!
Make sure that you're not making any manual configuration changes to either ViewLocator or ViewModelLocator after the calls to ConfigureTypeMappings(). These are static classes, so any configuration changes that you make are applied globally.
Oct 10, 2013 at 4:37 PM
This is a pretty bare-bones project, I've just been focusing on making the separation work.
I tried moving the code to the ViewModel. It fails (result is the null string):

namespace Mvvm.Cal.ViewModel
{
public class AppViewModel:PropertyChangedBase
{
   public AppViewModel()
   {

       var config = new TypeMappingConfiguration
       {
           DefaultSubNamespaceForViews = "View",
           DefaultSubNamespaceForViewModels = "ViewModel"

       };

       ViewLocator.ConfigureTypeMappings(config);
       ViewModelLocator.ConfigureTypeMappings(config);

       var typename = this.GetType().FullName;
       var typename2 = this.GetType().ToString();
       var result = String.Join(", ", ViewModelLocator.TransformName(typename, true).ToArray());

   }

}
}

Any ideas?
Oct 10, 2013 at 4:48 PM
Edited Oct 10, 2013 at 4:59 PM
I finally realized what you're doing wrong. If the code is being called from the ViewModel, then you should be using the ViewLocator to find the View. There is no need to call ViewModelLocator.TransformName(), because the ViewModel should never need to find itself.

This was actually the first thing I pointed out to you:
The main issue is that your code configured the ViewLocator while calling ViewModelLocator.TransformName().
Regardless, you should always configure both, but you were simply using the wrong class in your ViewModel code.
Nov 6, 2013 at 5:32 PM
Right! Simple, and obvious. (Once you know...)
Thanks!
Dec 19, 2013 at 3:12 PM
OK, now I'm trying to repeat the experiment - only with separate projects in the same solution.
Everything seems in order except at the point where CM is trying to find my view.

I start with:
protected override System.Collections.Generic.IEnumerable<System.Reflection.Assembly> SelectAssemblies()
    {
        List<Assembly> myAssemblies = new List<Assembly>();
        string _path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        string _viewFile = "View.dll";
        string _assemblyPath = string.Concat(_path, "\\", _viewFile);
        myAssemblies.Add(Assembly.LoadFile(_assemblyPath));
        myAssemblies.AddRange(base.SelectAssemblies());
        return myAssemblies;
    }
Then:
public AppBootstrapper()
    {
        // Configure Type Mapping to take separate folders into account
        var config = new TypeMappingConfiguration
                     {
                         DefaultSubNamespaceForViews = "View",
                         DefaultSubNamespaceForViewModels = "ViewModel"
                     };
        ViewLocator.ConfigureTypeMappings(config);
        ViewModelLocator.ConfigureTypeMappings(config);

        //
        // Test Type Mapping (Includes effect of overriding SelectAssemblies, below
        //
        // This code copied from http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions 
        //  View Resolution (ViewModel - First)
        //
        var modelTypeName = "MVVM2.ViewModel.ViewModel1";
        var viewTypeName = modelTypeName.Replace("Model", string.Empty);
        var result = String.Join(", ", ViewLocator.TransformName(modelTypeName, true).ToArray());

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

    }
This seems to show that for my ViewModel, MVVM2.ViewModel.ViewModel1, the correct View comes up - MVVM2.View.View1 - at least in the last line as "viewType."
However, in the "result" line, I get null.

What am I missing?
TIA
Rick
Dec 19, 2013 at 11:50 PM
Edited Dec 19, 2013 at 11:53 PM
The default convention is <BaseName><NameSuffix> (e.g. SomethingViewModel). Compare that to your example.

If you want to change the way names are composed, you need to set an additional property:
            var config = new TypeMappingConfiguration
            {
                DefaultSubNamespaceForViews = "View",
                DefaultSubNamespaceForViewModels = "ViewModel",
                NameFormat = "{1}{0}"
            };
Another thing is that "1" as a base name would create an illegal type name if the default composition order were used (e.g. 1ViewModel -- illegal name!). The ViewLocator and ViewModelLocator does RegEx pattern matching based on legal type names, so even though something like "ViewModel1" would be a legal name if a particular composition order is used, it would not be the case for the default composition order.

Aside from setting the NameFormat property as indicated above, you will need to use an example ViewModel type name like "MVVM2.ViewModel.ViewModelM1". This will result in a view of the type name "MVVM2.View.ViewM1".
Dec 23, 2013 at 9:30 PM
So if I add the NameFormat parameter as above, is there any reason why MVVM2.ViewModel.ViewModel1" wouldn't result in a view of the type name "MVVM2.View.View1"?
Thanks!
Dec 23, 2013 at 10:17 PM
rspiewak wrote:
So if I add the NameFormat parameter as above, is there any reason why MVVM2.ViewModel.ViewModel1" wouldn't result in a view of the type name "MVVM2.View.View1"?
Thanks!
I already explained above. If you have your heart set on using that name, then you'll need to use your own custom rule. Otherwise, just make it a legal name by starting the base name with an alpha character.
Dec 23, 2013 at 10:35 PM
Thanks. It would be nice if there was either more documentation or some test APIs to figure out what CM would do with certain inputs.
Dec 23, 2013 at 11:51 PM
rspiewak wrote:
Thanks. It would be nice if there was either more documentation or some test APIs to figure out what CM would do with certain inputs.
The "Handling Custom Conventions" of the documentation has many examples, one of which involves changing the order of the base name and suffix. As for the limitations on base names, that's a note that I suppose could be added to the documentation. However, I have to say this is bit of an edge case. Usually, you would give your ViewModels and Views meaningful names like CustomerViewModel (or ViewModelCustomer if you choose to flip the order). A name like "ViewModel1" is meaningless, and if you were to use the more common <base name><suffix> convention, the name "1ViewModel" would be both meaningless and illegal.
Dec 26, 2013 at 6:40 PM
OK, light finally dawned over marble head, I changed my naming conventions to conform to how CM actually works, and the problem was solved. At any rate, I did end up drilling down further so I understand it better.

Sometimes this is the problem - that it all becomes clear and you forget why it was hard to begin with. I still think some test-oriented APIs would help - a way to retrieve what the framework thinks are available mappings, maybe in plain text rather than regex form? While the extensive use of regex and Funcs is no doubt elegant and concise, it tends to make things a bit opaque when you're trying to understand and explore.

Thanks for your patience!