August 27th, 2010 | Categories: Programming | Tags: , ,

Over the last few weeks I have been writing some in-depth articles for my Learning Caliburn.Micro series. Today is a brief interlude where I introduce a ReSharper File Template to help with the task of generating view-models.

If this is the first time you are hearing about my Learning Caliburn.Micro series, here is the list of previous posts in case you need to catch up.

Background

So why do you need this template? Simple – to write those twenty or so lines of code you constantly have to do yourself. I will warn you – this template is highly opinionated. It assumes that you when you create you view-models, you have the ability to pass the IEventAggregator as a parameter. I use Autofac as the way all my view-models are created, so it deals with this dependency injection for me. If you use MEF, then you will need to add the appropriate ImportingConstructor attribute. If you use ReSharper this is easy enough. Just edit the template after you import it.

If you don’t want the IEventAggregator, or you want to another type, it is also that simple. Just modify the template accordingly. I have a few of these templates, each specialized for my use cases.

Usage

Now the fun part. How you use it. You need to import the following template:

If you don’t know how to do this, then I suggest you check out the Templates Galore: File Templates post from JetBrains.

Once it is imported, make any changes you want, or just copy it and change the copies.

Then use the ReSharper New from Template command, and select your template (or if you didn’t add it to your short list, use the More… option).

newTemplate

I chose a highly expressive name, and here is the result:

BaseClass

I used ReSharper’s macro functionality to provide a quick drop down of potential base classes. Just pick one and you are done.

Summary

As I work on the Learning Caliburn.Micro series, I hope to post some more quick helpers to complement the deeper functionality articles. This template is available on github, but not in my normal repository. Instead, it is posted as a gist, a git snippet, and is available at CaliburnMicroViewModel.cs.

Technorati Tags: ,,
August 20th, 2010 | Categories: Programming | Tags: , , ,

It has been about two weeks since my last post in my Learning Caliburn.Micro series, so it far past the time for this fifth post. If this is the first time you are hearing about this series, here is the list of previous posts in case you need to catch up.

Prepare the Project

This post is going to focus on using the Autofac IoC with Caliburn.Micro. From the point of view of the framework it fulfills the same task as the MEF bootstrapper I discussed in Caliburn.Micro the MEFtacluar, or Rob discussed in Caliburn.Micro Soup to Nuts Pt. 2 – Customizing The Bootstrapper. The difference is that it uses Autofac instead of MEF.

By now, you should know how to create a Caliburn.Micro WPF application. If not, please go back to the earlier posts and create a WPF application using standard conventions. I suggest including a logger, such as the DebugLogger to help you see the actions the framework, and the Autofac bootstrapper we will create are taking.

Last, add a reference to Autofac assembly to the project. You do not need the Autofac configuration assembly for this project. However, in your own projects you may want to use it, if you prefer XML based configuration. I tend to use the fluent interface, so I almost never use it. Also, I am using Autofac 2.2.4, as that is the most current version I have. Autofac is a very active project and there tends to be a release every couple of months, but there is rarely any breaking changes in the same major version.

Application BootStrapper class

You should have noticed that every Caliburn.Micro application will have a bootstrapper, so lets start with that class. Instead of working our way up to the to the application bootstrapper, we are going to start with it, just as the framework does.

1 using System; 2 using System.Collections.Generic; 3 using Caliburn.Micro; 4 using HelloAutofac.Util; 5 using HelloAutofac.ViewModels; 6 7 namespace HelloAutofac 8 { 9 public class MyBootStrapper : TypedAutofacBootStrapper<ShellViewModel> 10 { 11 #region Fields 12 private readonly ILog _logger = LogManager.GetLog(typeof(MyBootStrapper)); 13 #endregion 14 15 #region Constructor 16 static MyBootStrapper() 17 { 18 LogManager.GetLog = type => new DebugLogger(typeof(MyBootStrapper)); 19 } 20 #endregion 21 22 #region Overrides 23 protected override void ConfigureContainer(Autofac.ContainerBuilder builder) 24 { 25 _logger.Info("Configuring Container."); 26 base.ConfigureContainer(builder); 27 28 // good place to register application types or custom modules 29 //builder.RegisterModule<RegistrationModule>(); 30 } 31 #endregion 32 } 33 }

Our new MyBootStrapper does not look much different from the MEFBootStrapper. We will cover the new base class a little later in the post, but I want to point out the generic argument to it. This replaces the IShell we used to tag our root view model when we used MEF. The other item to notice is the ConfigureContainer method. This override allows you to add your own registrations to the Autofac container during the bootstrapper configuration process.

Lets also ensure the bootstrapper is created. This should pretty familiar by now as well.

1 <Application x:Class="HelloAutofac.App" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:HelloAutofac"> 5 <Application.Resources> 6 <ResourceDictionary> 7 <ResourceDictionary.MergedDictionaries> 8 <ResourceDictionary> 9 <local:MyBootStrapper x:Key="bootstrapper" /> 10 </ResourceDictionary> 11 </ResourceDictionary.MergedDictionaries> 12 </ResourceDictionary> 13 </Application.Resources> 14 </Application>

The TypedAutofacBootStrapper Class

Time for the class that does all the work. Notice that I called this bootstrapper Typed. That is because it will take the type of view-model explicitly, similar to the generic Bootstrapper<> provided by Caliburn.Micro. You can still use the IShell model if you choose, by I didn’t see the need for another new convention.

Lets dissect the class in detail, and then I will provide the whole thing.

1 public class TypedAutofacBootStrapper<TRootViewModel> : Bootstrapper<TRootViewModel> 2 { 3 #region Fields 4 private readonly ILog _logger = LogManager.GetLog(typeof(TypedAutofacBootStrapper<>)); 5 private IContainer _container; 6 #endregion 7 8 #region Properties 9 protected IContainer Container 10 { 11 get { return _container; } 12 } 13 #endregion

The class is derived off the generic bootstrapper because I can reuse the DisplayRootView logic with Autofac. I also expose the autofac container as a protected property so it is directly available in the derived bootstrappers. I choose to do this because I do occasionally use child containers for managing resource bound work, and the Caliburn.Micro IoC abstraction does not provide that functionality. I also did not want to add that functionality since I only use it specific use cases and not as norm.

I override the following four methods.

Configure

1 protected override void Configure() 2 { // configure container 3 var builder = new ContainerBuilder(); 4 5 // register view models 6 builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) 7 // must be a type that ends with ViewModel 8 .Where(type => type.Name.EndsWith("ViewModel")) 9 // must be in a namespace ending with ViewModels 10 .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("ViewModels")) 11 // must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this) 12 .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null) 13 // registered as self 14 .AsSelf() 15 // always create a new one 16 .InstancePerDependency(); 17 18 // register views 19 builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) 20 // must be a type that ends with View 21 .Where(type => type.Name.EndsWith("View")) 22 // must be in a namespace that ends in Views 23 .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("Views")) 24 // registered as self 25 .AsSelf() 26 // always create a new one 27 .InstancePerDependency(); 28 29 // register the single window manager for this container 30 builder.Register<IWindowManager>(c => new WindowManager()).InstancePerLifetimeScope(); 31 // register the single event aggregator for this container 32 builder.Register<IEventAggregator>(c => new EventAggregator()).InstancePerLifetimeScope(); 33 34 ConfigureContainer(builder); 35 36 _container = builder.Build(); 37 }

This is where the magic of registration happens. Those familiar with autofac should recognize what is going on, but for those not familiar, here is a break down.

First, I create a ContainerBuilder (line 3) which is the class used to configure an autofac container. The I register all types found in any of the assemblies in the AssemblySource (remember this one from the MEFBootStrapper?) where the name of the type ends in ‘ViewModel’ and the namespace of the type ends with ‘ViewModels’ and the type implements the INotifyPropertyChanged interface. In hindsight, I should probably add logic to ensure the type is a non-abstract class with a public constructor, but I will save that for a future post. Line 14 states that the type should be registered as its own type, so a type of ShellViewModel will be registered as ShellViewModel, and not as one of its implemented interfaces. Autofac does allow us to register those as well if we want, but the view-model was all I needed. Line 16 states that autofac should create a new instance every time I ask for this view-model.

Now that the view-models are registered, we need to register the views. This begins on line 19. I use the same group of assemblies, and this time restrict the types to those with named ending in ‘View’ that are also in namespaces ending in ‘Views’. These are also registered as themselves, and are also newly created every time they are requested from the container. Then we register the WindowManager and EventAggregator services as singletons for that container.

Line 34 is a call to the new virtual method that provides your code a place to register your own types using the ContainerBuilder. The Autofac model favors an immutable container approach, and you should register all your types before the container is built. The container gets built in the next step, and stored in a field.

GetInstance

1 protected override object GetInstance(Type serviceType, string key) 2 { 3 if (string.IsNullOrWhiteSpace(key)) 4 { 5 if (Container.IsRegistered(serviceType)) 6 return Container.Resolve(serviceType); 7 } 8 else 9 { 10 if (Container.IsRegistered(key, serviceType)) 11 return Container.Resolve(key, serviceType); 12 } 13 throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? serviceType.Name)); 14 }

This method is how Caliburn.Micro retrieves an instance from the container. No real rocket-science here, just some logic to retrieved unnamed types as well as named typed.

GetAllInstances

1 protected override IEnumerable<object> GetAllInstances(Type serviceType) 2 { 3 return Container.Resolve(typeof(IEnumerable<>).MakeGenericType(serviceType)) as IEnumerable<object>; 4 }

Simple wrapper to retrieve a collection of all instances that implement the requested type.

BuildUp

1 protected override void BuildUp(object instance) 2 { 3 Container.InjectProperties(instance); 4 }

This method allows the container to inject dependencies into an already created instance. In the case of autofac, this is restricted to properties.

ConfigureContainer

1 protected virtual void ConfigureContainer(ContainerBuilder builder) 2 { 3 }

This is the virtual method you can override to configure the container.

Full Implementation

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Linq; 5 using System.Text; 6 using Autofac; 7 using Caliburn.Micro; 8 using IContainer = Autofac.IContainer; 9 10 namespace HelloAutofac.Util 11 { 12 public class TypedAutofacBootStrapper<TRootViewModel> : Bootstrapper<TRootViewModel> 13 { 14 #region Fields 15 private readonly ILog _logger = LogManager.GetLog(typeof(TypedAutofacBootStrapper<>)); 16 private IContainer _container; 17 #endregion 18 19 #region Properties 20 protected IContainer Container 21 { 22 get { return _container; } 23 } 24 #endregion 25 26 #region Overrides 27 protected override void Configure() 28 { // configure container 29 var builder = new ContainerBuilder(); 30 31 // register view models 32 builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) 33 // must be a type that ends with ViewModel 34 .Where(type => type.Name.EndsWith("ViewModel")) 35 // must be in a namespace ending with ViewModels 36 .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("ViewModels")) 37 // must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this) 38 .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null) 39 // registered as self 40 .AsSelf() 41 // always create a new one 42 .InstancePerDependency(); 43 44 // register views 45 builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) 46 // must be a type that ends with View 47 .Where(type => type.Name.EndsWith("View")) 48 // must be in a namespace that ends in Views 49 .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("Views")) 50 // registered as self 51 .AsSelf() 52 // always create a new one 53 .InstancePerDependency(); 54 55 // register the single window manager for this container 56 builder.Register<IWindowManager>(c => new WindowManager()).InstancePerLifetimeScope(); 57 // register the single event aggregator for this container 58 builder.Register<IEventAggregator>(c => new EventAggregator()).InstancePerLifetimeScope(); 59 60 ConfigureContainer(builder); 61 62 _container = builder.Build(); 63 } 64 protected override object GetInstance(Type serviceType, string key) 65 { 66 if (string.IsNullOrWhiteSpace(key)) 67 { 68 if (Container.IsRegistered(serviceType)) 69 return Container.Resolve(serviceType); 70 } 71 else 72 { 73 if (Container.IsRegistered(key, serviceType)) 74 return Container.Resolve(key, serviceType); 75 } 76 throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? serviceType.Name)); 77 } 78 protected override IEnumerable<object> GetAllInstances(Type serviceType) 79 { 80 return Container.Resolve(typeof(IEnumerable<>).MakeGenericType(serviceType)) as IEnumerable<object>; 81 } 82 protected override void BuildUp(object instance) 83 { 84 Container.InjectProperties(instance); 85 } 86 #endregion 87 88 protected virtual void ConfigureContainer(ContainerBuilder builder) 89 { 90 } 91 } 92 }

Summary

The work to get autofac configured was not much more than wiring up MEF. I am pretty sure that this process would be similar for most other IoC containers, and I am thinking about doing one more just to prove it. One of the joys of working with Caliburn.Micro is how easy it can be extended, and how quick it is to get up and running. This was no exception.

Now that we have the basic infrastructure out of the way, with logging, MEF, and IoC. The next set of posts will start focusing on feature sets. The posts coming up will be on visualizing collections with lists & trees, eventing, multiple views, master-detail views, and multiple views per view-model.

One project note. I have modified the solution to directly include Caliburn.Micro. A few people requested it, and it really does make it easier to debug into the framework.

As a reminder, all of the source code for this project is located in my Github Learning Caliburn.Micro repository.

Technorati Tags: ,,,
August 8th, 2010 | Categories: Programming | Tags: , , , ,

This is the fourth entry in my Learning Caliburn.Micro series. Since I do build on content from earlier posts, here are the links in case you need to catch up.

Caliburn.Micro Logging Abstraction

This post covers logging. Caliburn.Micro has a simple logging abstraction that consists of an ILog interface, and a LogManager class (see below).

1 /// <summary> 2 /// A logger. 3 /// </summary> 4 public interface ILog 5 { 6 /// <summary> 7 /// Logs the message as info. 8 /// </summary> 9 /// <param name="format">A formatted message.</param> 10 /// <param name="args">Parameters to be injected into the formatted message.</param> 11 void Info(string format, params object[] args); 12 13 /// <summary> 14 /// Logs the message as a warning. 15 /// </summary> 16 /// <param name="format">A formatted message.</param> 17 /// <param name="args">Parameters to be injected into the formatted message.</param> 18 void Warn(string format, params object[] args); 19 20 /// <summary> 21 /// Logs the exception. 22 /// </summary> 23 /// <param name="exception">The exception.</param> 24 void Error(Exception exception); 25 }

1 /// <summary> 2 /// Used to manage logging. 3 /// </summary> 4 public static class LogManager 5 { 6 static readonly ILog NullLogInstance = new NullLog(); 7 8 /// <summary> 9 /// Creates an <see cref="ILog"/> for the provided type. 10 /// </summary> 11 public static Func<Type, ILog> GetLog = type => NullLogInstance; 12 13 private class NullLog : ILog 14 { 15 public void Info(string format, params object[] args) { } 16 public void Warn(string format, params object[] args) { } 17 public void Error(Exception exception) { } 18 } 19 }

To add logging to any Caliburn.Micro project, you need to implement a class that extends the ILog interface, and you need to replace the delegate for GetLog. In this post, I will show you how to hook up a debug logger that logs to the Output window in Visual Studio, a log4net logger, and an NLog logger. For other loggers, it would be a simple matter to modify these examples to support them.

As always, all of the source code for this project is located in my Github Learning Caliburn.Micro repository.

So lets get started. As with any Caliburn.Micro project, you need a new WPF project with some directories named according to the targetframeworkconvention (Views and ViewModels). See my post Caliburn.Micro – Hello World for the basics of creating a project. Now, lets switch the Target Framework from the Client Profile to just .NET Framework. This is on the Application tab for the project settings. We need to do this because the logging frameworks do not work with the Client Profile at this time.

Debug Logger

The first logger we will implement is the debug logger. The goal of this logger is output its messages to System.Diagnostics.Debug which will show up in the Visual Studio Output window. I chose to place my loggers in a ‘Utils’ directory off the main project, and creatively called it the DebugLogger. Here it is.

1 class DebugLogger : ILog 2 { 3 #region Fields 4 private readonly Type _type; 5 #endregion 6 7 #region Constructors 8 public DebugLogger(Type type) 9 { 10 _type = type; 11 } 12 #endregion 13 14 #region Helper Methods 15 private string CreateLogMessage(string format, params object[] args) 16 { 17 return string.Format("[{0}] {1}", 18 DateTime.Now.ToString("o"), 19 string.Format(format, args)); 20 } 21 #endregion 22 23 #region ILog Members 24 public void Error(Exception exception) 25 { 26 Debug.WriteLine(CreateLogMessage(exception.ToString()), "ERROR"); 27 } 28 public void Info(string format, params object[] args) 29 { 30 Debug.WriteLine(CreateLogMessage(format, args), "INFO"); 31 } 32 public void Warn(string format, params object[] args) 33 { 34 Debug.WriteLine(CreateLogMessage(format, args), "WARN"); 35 } 36 #endregion 37 }

I also chose to ignore the Type parameter being passed in, other than to store it. Usually when I am using the debug logger, I find that the type is overkill in the message flow.

The next thing you need to do is set the delegate. I do this in a static constructor for the bootstrapper since this is called before any of the Caliburn.Micro code, or my own code is called. Here is the bootstrapper, also with a highly creative name.

1 public class MyBootStrapper : Bootstrapper<ShellViewModel> 2 { 3 static MyBootStrapper() 4 { 5 LogManager.GetLog = type => new DebugLogger(type); 6 } 7 }

Believe it or not, that’s it. Logging is now hooked up, and here is what a default run will look like.

1 INFO: [2010-08-08T15:28:18.8063954-04:00] Binding System.Windows.Window+LogJam.ViewModels.ShellViewModel. 2 INFO: [2010-08-08T15:28:18.8263965-04:00] Setting DC of System.Windows.Window to LogJam.ViewModels.ShellViewModel. 3 INFO: [2010-08-08T15:28:18.8283966-04:00] Attaching message handler LogJam.ViewModels.ShellViewModel to System.Windows.Window. 4 INFO: [2010-08-08T15:28:18.8343970-04:00] No bindable control for action CanGenerateLogMessage. 5 INFO: [2010-08-08T15:28:18.8353970-04:00] Added convention action for GenerateLogMessage as GenerateLogMessage(logTypes,logMessage). 6 INFO: [2010-08-08T15:28:18.8843999-04:00] No bindable control for action add_PropertyChanged. 7 INFO: [2010-08-08T15:28:18.8853999-04:00] No bindable control for action remove_PropertyChanged. 8 INFO: [2010-08-08T15:28:18.8864000-04:00] No bindable control for action NotifyOfPropertyChange. 9 INFO: [2010-08-08T15:28:18.8874000-04:00] No bindable control for action NotifyOfPropertyChange. 10 INFO: [2010-08-08T15:28:18.8884001-04:00] No bindable control for action RaisePropertyChangedEventImmediately. 11 INFO: [2010-08-08T15:28:18.8884001-04:00] No bindable control for action ToString. 12 INFO: [2010-08-08T15:28:18.8894001-04:00] No bindable control for action Equals. 13 INFO: [2010-08-08T15:28:18.8904002-04:00] No bindable control for action GetHashCode. 14 INFO: [2010-08-08T15:28:18.8904002-04:00] No bindable control for action GetType. 15 INFO: [2010-08-08T15:28:18.9314025-04:00] Action: GenerateLogMessage availability update.

The Caliburn.Micro framework starts using our new logging right away, and with some very useful information. Once thing to notice is that it always starts with the binding of the root view-model. You can see logging is done for some initialization tasks, binding, and of course – actions.

One last thing before moving on to the other loggers, and that is how to use the logger for your own code. Happily, just like you would any of the major logging frameworks. Just call LogManager.GetLog, and you get back a logger. Then just call Info, Warn, or Error.

1 private readonly ILog _log = LogManager.GetLog(typeof(ShellViewModel));

For those used to log4net and nlog, you will notice that the Caliburn.Micro ILog interface does not offer as many logging modes. If you find yourself needing those, then derive from ILog a new interface that adds those extra methods back in, and have your GetLog delegate return a class that implements it. Caliburn.Micro will still use the three methods it knows about, but you are free to use your extra methods.

Log4net Logger

Log4net is the old guard of logging in the .NET community. It has probably been used in more projects than any others, and is always the first one I hear about from people when the topic of logging comes up. So, I decided that this would be the first I would implement. Log4net has a few things needed to make it work, but first the logger class in which you use it.

1 class Log4netLogger : ILog 2 { 3 #region Fields 4 private readonly log4net.ILog _innerLogger; 5 #endregion 6 7 #region Constructors 8 public Log4netLogger(Type type) 9 { 10 _innerLogger = log4net.LogManager.GetLogger(type); 11 } 12 #endregion 13 14 #region ILog Members 15 public void Error(Exception exception) 16 { 17 _innerLogger.Error(exception.Message, exception); 18 } 19 public void Info(string format, params object[] args) 20 { 21 _innerLogger.InfoFormat(format, args); 22 } 23 public void Warn(string format, params object[] args) 24 { 25 _innerLogger.WarnFormat(format, args); 26 } 27 #endregion 28 }

Two more things are needed to support log4net. You need to tell it how to configure itself. I usually use the app.config model for log4net, and I place the following attribute into the AssemblyInfo.cs file.

1 // Configure log4net using the app.config file 2 [assembly: log4net.Config.XmlConfigurator(Watch = true)]

Then I add the following to app.config inside the configuration tag.

1 <configSections> 2 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> 3 </configSections> 4 5 <log4net> 6 <appender name="TraceAppender" type="log4net.Appender.TraceAppender"> 7 <layout type="log4net.Layout.PatternLayout"> 8 <conversionPattern value="%date [%thread] %-5level - %message%newline" /> 9 </layout> 10 </appender> 11 <appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender" > 12 <layout type="log4net.Layout.PatternLayout"> 13 <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> 14 </layout> 15 </appender> 16 <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > 17 <layout type="log4net.Layout.PatternLayout"> 18 <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" /> 19 </layout> 20 </appender> 21 <root> 22 <level value="DEBUG" /> 23 <appender-ref ref="TraceAppender" /> 24 </root> 25 </log4net>

One last change, you need to tell Caliburn.Micro to use the log4net logger by modifying the line in the static constructor for bootstrapper.

1 LogManager.GetLog = type => new Log4netLogger(type);

At this point, log4net is hooked up and you can use it throughout your code.

NLog Logger

At this point you should have a good idea of the changes needed to implement NLog support. Here is the code.

1 class NLogLogger : ILog 2 { 3 #region Fields 4 private readonly NLog.Logger _innerLogger; 5 #endregion 6 7 #region Constructors 8 public NLogLogger(Type type) 9 { 10 _innerLogger = NLog.LogManager.GetLogger(type.Name); 11 } 12 #endregion 13 14 #region ILog Members 15 public void Error(Exception exception) 16 { 17 _innerLogger.ErrorException(exception.Message, exception); 18 } 19 public void Info(string format, params object[] args) 20 { 21 _innerLogger.Info(format, args); 22 } 23 public void Warn(string format, params object[] args) 24 { 25 _innerLogger.Warn(format, args); 26 } 27 #endregion 28 }

As you can tell, almost no change from the log4net version. NLog does not need the attribute telling it where the configuration file is, since it follows a conventions based approach to finding it. In this case, I chose to configure it in app.config as well.

1 <configSections> 2 <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/> 3 </configSections> 4 5 <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 6 <targets> 7 <target name="tr" xsi:type="Trace"/> 8 </targets> 9 <rules> 10 <logger name="*" writeTo="tr" /> 11 </rules> 12 </nlog>

Last, we need to modify the bootstrapper.

1 LogManager.GetLog = type => new NLogLogger(type);

So that’s it for NLog. It is now ready to be used.

Caveats

I have run into two caveats while working with Caliburn.Micro’s logging infrastructure that I want to share.

The first is kind of obvious, but I just want to make sure your aware, the logging is not available until after you set the LogManager.GetLog delegate to create your logger. Prior to that everything goes to the NullLogger. In my case, I create it in the bootstrapper static constructor, but this may not be early enough if you do anything in the App class before your bootstrapper is referenced. You may need to move the assignment of the delegate into the App static constructor.

The second issue I ran into is not specific to Caliburn.Micro, but to NLog. If I pass the type into NLog’s LogManager.GetLogger() along with the type.Name, I end up in an infinite loop and blow up my stack frame. I am not exactly sure why this happened as I opted not to spend the time to solve it. So I am providing this caveat as a warning against doing it yourself.

Conclusion

Caliburn.Micro makes it very easy to wire up any number of logging infrastructures. I detailed three different ones here, but can be expanded to support the Microsoft Enterprise Library Logging Application Block, or some proprietary logging framework.

I will be leveraging the loggers heavily as we move forward into hooking up Autofac as IoC, adding support for custom third-party controls, and even dealing with custom chrome windows.

As a reminder, all of the source code for this project is located in my Github Learning Caliburn.Micro repository.

August 4th, 2010 | Categories: Programming | Tags: , , ,

I was going to call this post ‘How I learned to stop worrying and love MEF.’, but I am not sure enough people would get the reference. Besides, it would put the focus too much on MEF, and not on Caliburn.Micro, which is the topic of the post.

This is the second in my series of using Caliburn.Micro for WPF applications. The first post, Caliburn.Micro – Hello World, covered an introduction to setting up a project, and the basics of view and view-model interaction. This week I am going to introduce how to make your applications more loosely coupled by leveraging MEF. As this series progresses, we will be leveraging MEF more, so I wanted to cover it early on.

Rob Eisenberg posted a MEFbootstrapper in the Support for MEF discussion on the Caliburn.Micro CodePlex site, and on his blog Caliburn.Micro Soup to Nuts Pt. 2 – Customizing The Bootstrapper. This article starts with a new project copied from the Hello World project of my last post. We then add Rob’s bootstrapper, and modify the project to demonstrate its use.

Before adding the MEF support, lets add a new interface called IShell. This will be the contract used by MEF to find our view-models. I place my contracts in a Contracts directory, but that is up to you. Here is the code to IShell. Right now, it is just a placeholder, but later on we will use for some more interesting things.

1 namespace HelloMef.Contracts 2 { 3 public interface IShell 4 { 5 } 6 }

So now on to MEF. First thing we need to do when we use MEF is reference System.ComponentModel.Composition. So go ahead and add that reference. Next, we need the MEFBootStrapper. Once again all the hard work has been done by Rob. However, the one he has on his blog, and in the Caliburn.Micro discussion forum works with Silverlight, but it does not work as is with WPF. I will post my version, then explain the difference.

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.Composition; 4 using System.ComponentModel.Composition.Hosting; 5 using System.ComponentModel.Composition.Primitives; 6 using System.Linq; 7 using System.Reflection; 8 using Caliburn.Micro; 9 using HelloMef.Contracts; 10 11 namespace HelloMef 12 { 13 public class MefBootStrapper : Bootstrapper<IShell> 14 { 15 #region Fields 16 private CompositionContainer _container; 17 #endregion 18 19 #region Overrides 20 protected override void Configure() 21 { // configure container 22 #if SILVERLIGHT 23 _container = CompositionHost.Initialize( 24 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>())); 25 #else 26 _container = new CompositionContainer( 27 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>())); 28 #endif 29 30 var batch = new CompositionBatch(); 31 32 batch.AddExportedValue<IWindowManager>(new WindowManager()); 33 batch.AddExportedValue<IEventAggregator>(new EventAggregator()); 34 batch.AddExportedValue(_container); 35 36 _container.Compose(batch); 37 } 38 protected override object GetInstance(Type serviceType, string key) 39 { 40 string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key; 41 var exports = _container.GetExportedValues<object>(contract); 42 43 if (exports.Count() > 0) 44 return exports.First(); 45 46 throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract)); 47 } 48 protected override IEnumerable<object> GetAllInstances(Type serviceType) 49 { 50 return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType)); 51 } 52 protected override void BuildUp(object instance) 53 { 54 _container.SatisfyImportsOnce(instance); 55 } 56 #endregion 57 } 58 }

So here is the difference, beyond the use of regions and my own personal coding style of using a leading underscore for fields. Lines 22-28 are a little different. Here is the original version that Rob posted:

1 _container = CompositionHost.Initialize( 2 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));

In Silverlight, MEF is initialized using CompositionHost.Initialize(), and in WPF we new up a CompositionContainer. To fix this, I just wrapped Rob’s original code in a Silverlight compile time check. While this class is not part of the Caliburn.Micro, it is first on my list for addition into a Caliburn.Micro.Contrib project.

We are almost there. Three more changes, and we will be a MEF enabled application. First, lets open up the MainViewModel.cs file. We need to add the IShell type as a base interface for the MainViewModel class. Then we need to add the MEF attribute to export the MainViewModel as type IShell. Here is the new version of MainViewModel.

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.Composition; 4 using System.Linq; 5 using System.Text; 6 using Caliburn.Micro; 7 using HelloMef.Contracts; 8 9 namespace HelloMef.ViewModels 10 { 11 [Export(typeof(IShell))] 12 public class MainViewModel : PropertyChangedBase, IShell 13 { 14 private string _name; 15 private string _helloString; 16 17 public string Name 18 { 19 get { return _name; } 20 set 21 { 22 _name = value; 23 NotifyOfPropertyChange(() => Name); 24 NotifyOfPropertyChange(() => CanSayHello); 25 } 26 } 27 public string HelloString 28 { 29 get { return _helloString; } 30 private set 31 { 32 _helloString = value; 33 NotifyOfPropertyChange(() => HelloString); 34 } 35 } 36 public bool CanSayHello 37 { 38 get { return !string.IsNullOrWhiteSpace(Name); } 39 } 40 public void SayHello(string name) 41 { 42 HelloString = string.Format("Hello {0}.", Name); 43 } 44 } 45 }

Finally, open up App.xaml. Remember back in Caliburn.Micro – Hello World we started the framework by having the application class instantiate our bootstrapper class on start up. We need to change the name of the bootstrapper class from HelloWorldBootStrapper to MefBootStrapper. Here is the new App.xaml:

1 <Application x:Class="HelloMef.App" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:HelloMef"> 5 <Application.Resources> 6 <ResourceDictionary> 7 <ResourceDictionary.MergedDictionaries> 8 <ResourceDictionary> 9 <local:MefBootStrapper x:Key="bootstrapper" /> 10 </ResourceDictionary> 11 </ResourceDictionary.MergedDictionaries> 12 </ResourceDictionary> 13 </Application.Resources> 14 </Application>

That’s it. We are done. Build and run the application and you should see the familiar program we did in the last post. The difference is that our coupling has been reduced so that the framework can now find our main view model by the contract IShell, and from there it can start our application. Over the next few posts we will add logging support, multiple views, third-party controls, and maybe a surprise or two. So stay tuned.

As a reminder, all of the source code for this project is located in my Github Learning Caliburn.Micro repository for this project.

Technorati Tags: ,,,
August 1st, 2010 | Categories: Programming | Tags: , ,

In this post we are going to create a simple WPF application using Caliburn.Micro.

I have created an empty Visual Studio 2010 solution for all the projects in this blog series. All of the code for this series will be available on my Github Learning Caliburn.Micro repository. But I suggest you follow along, and try and do it yourself.

  1. Start Visual Studio, and create a new WPF Application project called HelloWorld.
  2. Add references needed for Caliburn.Micro (Caliburn.Micro.dll and System.Windows.Interactivity.dll).
  3. Delete the MainWindow.xaml file.
  4. Create a directory named Views, and a directory named ViewModels.
  5. Create a user control in the views directory named MainView.
  6. Create a class in the ViewModels directory named MainViewModel.
  7. Create a class in the project root named HelloWorldBootStrapper.

Your project should now look like this:

ProjectSetup

That is the basic structure for a simple MVVM WPF application using Caliburn.Micro. There are other directories and files needed for more complex applications, but this is the basic starting point. So lets get to the code.

First, lets edit the HelloWorldBoothStrapper class. Make the class public, if it is not already, then derive it from Bootstrapper<MainViewModel>. You will need using statements to Caliburn.Micro and HelloWorld.ViewModels. Here is what the class should look like:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Caliburn.Micro; 6 using HelloWorld.ViewModels; 7 8 namespace HelloWorld 9 { 10 public class HelloWorldBootStrapper : Bootstrapper<MainViewModel> 11 { 12 } 13 }

Now we need to clean up App.xaml. Open that file for editing, and remove the StartUpi attribute from the Application tag. Next we need to add a namespace reference to the HelloWorld namespace, and add a resource dictionary that instantiates our bootstrapper class. Here is the final code:

1 <Application x:Class="HelloWorld.App" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:HelloWorld"> 5 <Application.Resources> 6 <ResourceDictionary> 7 <ResourceDictionary.MergedDictionaries> 8 <ResourceDictionary> 9 <local:HelloWorldBootStrapper x:Key="bootstrapper" /> 10 </ResourceDictionary> 11 </ResourceDictionary.MergedDictionaries> 12 </ResourceDictionary> 13 </Application.Resources> 14 </Application>

The application class is the normal starting point of a WPF application, and what we have done is asked to create our bootstrapper when it starts, but nothing else. This is how we start Caliburn.Micro, which in turn starts running our code. We will revisit the bootstrapper is later posts, as it is where we can help tune our applications behavior, and add support of other common tools we may want to use like MEF or logging.

Up until now, we have been structuring our application to conform to the conventions that Caliburn.Micro uses as its default behavior. When working with Caliburn.Micro, you will learn that it adheres to a programming practice of Convention over Configuration. I won’t go into the specifics of that model, but strongly suggest watching Rob’s Mix 10 presentation Build Your Own MVVM Framework. It covers his thinking on the subject, and how Caliburn.Micro leverages that to minimize the amount of code needed to build an application.

Now lets actually build and run our application.ApplicationStep1Exciting isn’t it? Ok. I kid. But I think it exhibits an important point. Where did the window come from? We deleted MainWindow.xaml. We did add a MainView user control, but we didn’t create that anywhere. So, I ask again, where did the window come from? The answer is, of-course, the framework (if it wasn’t, then this blog post would be really weird). So the real question is how did the framework know what to do. Let’s follow the flow. The application starts and creates the Application class (App.xaml). The XAML code for this class created an instance of our HelloWorldBootStrapper. The HelloWorldBootStrapper is derived from the Bootstrapper class (the first framework class we interact with), and this takes the type MainViewModel as the generic parameter. This is how the framework identifies the first view model to be created. The framework then removes the word ‘Model’ from MainViewModel to get the possible view name of MainView. It also removes the word ‘Model’ from the namespace HelloWorld.ViewModels to get the possible view namespace of HelloWorld.Views. The framework then checks for a view with the following type name ‘HelloWorld.Views.MainView’. If we look at our project, that is the type of our user control. The framework does some more magic to make this into a window and display it, but the important thing is that it found our view, bound that view to viewmodel, and then displayed it as the content of the window.

We are almost done now, so lets add some basic content. Open up the MainViewModel. Derive the class from PropertyChangedBase, which will require a using reference to Caliburn.Micro. Lets add some properties, and a command.

Add two private fields, _name and _helloString, both of type string.

1 private string _name; 2 private string _helloString;

Then add two properties Name and HelloString that implement the get and set for those two fields (Note: do not use automatic properties). When the value is set, call the method NotifyOfPropertyChance referencing the property. In the case of setting the Name, you also need to call NotifyOfPropertyChance on the property CanSayHello.

1 public string Name 2 { 3 get { return _name; } 4 set 5 { 6 _name = value; 7 NotifyOfPropertyChange(() => Name); 8 NotifyOfPropertyChange(() => CanSayHello); 9 } 10 } 11 12 public string HelloString 13 { 14 get { return _helloString; } 15 private set 16 { 17 _helloString = value; 18 NotifyOfPropertyChange(() => HelloString); 19 } 20 }

Add a property called CanSayHello of type bool, that tests Name for null or whitespace and returns true if it is not.

return !string.IsNullOrWhiteSpace(Name);

Now add the method called SayHello.

1 public void SayHello(string name) 2 { 3 HelloString = string.Format("Hello {0}.", Name); 4 }

This example should look familiar if you have read Rob’s Caliburn.Micro Soup to Nuts Pt. 1 – Configuration, Actions and Conventions. I wanted to stick close to his Silver Light example to allow for comparison between WPF and Silver Light.

Your MainViewModel.cs file should look like this:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Caliburn.Micro; 6 7 namespace HelloWorld.ViewModels 8 { 9 public class MainViewModel : PropertyChangedBase 10 { 11 private string _name; 12 private string _helloString; 13 14 public string Name 15 { 16 get { return _name; } 17 set 18 { 19 _name = value; 20 NotifyOfPropertyChange(() => Name); 21 NotifyOfPropertyChange(() => CanSayHello); 22 } 23 } 24 25 public string HelloString 26 { 27 get { return _helloString; } 28 private set 29 { 30 _helloString = value; 31 NotifyOfPropertyChange(() => HelloString); 32 } 33 } 34 35 public bool CanSayHello 36 { 37 get { return !string.IsNullOrWhiteSpace(Name); } 38 } 39 40 public void SayHello(string name) 41 { 42 HelloString = string.Format("Hello {0}.", Name); 43 } 44 } 45 }

We are done with the model, so lets modify the view. Open up the MainView.xaml file. For simplicity I changed the Grid into a StackPanel. Now lets add some controls. Add a StackPanel with the Orientation attribute set to Horizontal. Then add a TextBlock with the Text attribute set to “Name: “, and a TextBox with the x:Name attribute set to “Name”. Close off the inner StackPanel. Add another TextBlock with the x:Name attribute set to “HelloString”. Then add a Button control with the x:Name attribute set to “SayHello” and the Content attribute set to “Click Me”. Finally, close off the StackPanel. Your MainView.xaml should look like this:

1 <UserControl x:Class="HelloWorld.Views.MainView" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 mc:Ignorable="d" 7 d:DesignHeight="300" d:DesignWidth="500"> 8 <StackPanel> 9 <StackPanel Orientation="Horizontal"> 10 <TextBlock Text="Name: " /> 11 <TextBox x:Name="Name" /> 12 </StackPanel> 13 <TextBlock x:Name="HelloString"/> 14 <Button x:Name="SayHello" Content="Click Me"/> 15 </StackPanel> 16 </UserControl>

HelloWorldApp-StartupThat’s it. We are done. Build and run the application. It should look like this. Nothing spectacular as it reflects the XAML code you typed in. However, pay close attention to the button. Notice it is disabled. Take a look at the XAML and you will see you that nowhere did you write any code to do that. In fact, even in the view-model code you didn’t write any code to disable the button. This is the real value add of the Caliburn.Micro framework. It made some assumptions based on convention, and it was able to auto-wire the view and the view-model in the binding process (see the bootstrapper above). The conventions applied here are designed to map the type of control, the name of the control (see x:Name), and either properties or methods in the view-model. Here are the bindings that the framework did for us:

View (XAML)

ViewModel

Control Property Property / Method Data Type
TextBox Text Name string
TextBlock Text HelloString string
Button IsEnabled CanSayHello boolean
Button Click Event SayHello method

These bindings were done for us by the framework using some basic conventions. In the case of the TextBox and TextBlock, the framework matched the x:Name with properties of the same name on the view-model. It used the data type of the properties to identify the right way to handle the binding (in other scenarios it will auto-invoke converters or data templates, for example). The button is a much more interesting situation. It matched the x:Name of the button with the name of a method on the view-model, and then created a command object (class implementing ICommand) to bind the two to each other. However, the framework employed one more convention. It looked for a property that was the same name as the method with ‘Can’ as a prefix. This is then used by the command object to determine if the button is enabled.

HelloWorldApp-Clicked Now go back to the running window and type your name in to the TextBox. You will notice that the button is enabled right after the first character is entered. That is because the CanSayHello property on our view-model says that if Name property has any value that is not null, all whitespace, or empty, then it is valid. Click the button (you know you want to). The TextBlock is now populated. That happened because the view was notified of the change to the HelloString in the view-model. That change was made the same way the view knew about the Name changing, and that the CanSayHello property had changed. Look back into the MainViewModel. Notice the calls to the method NotifyOfPropertyChange? That method uses a bit of expression magic to identify the name of the property. I will go into that magic in another post. For now, just accept it works (since it does). Back to the method NotifyOfPropertyChange. It is inherited from the base class, PropertyChangedBase. This base class implements INotifyPropertyChanged which is what WPF (and Silver Light) uses to listen to changes in the value of a property. Calling that method just triggers that process which is part of the basic glue code of any WPF based application (with or without MVVM).

So that’s it. The basic MVVM application using Caliburn.Micro. In future blog posts we will build upon this basic application, integrating IoC containers and third-party component libraries, and increasing the complexity of the application.

As a reminder, all of the source code for this project is located in my Github Learning Caliburn.Micro repository for this project.

Technorati Tags: ,,