16
Oct
12

Coroutines are dead. Long live Coroutines.

As many of you may have already heard, Caliburn.Micro was recently ported to WinRT thanks to Nigel Sampson and Keith Patton.

Either if you are doing Windows 8 development or working with WPF and .NET Framework 4.5, you are probably eagerly playing working with the powerful asynchronous programming support in Visual Studio 2012. (Not yet? Go study it NOW).

For those using Caliburn and Caliburn.Micro, a very neat kind of asynchronous programming has been available since 2009: have a look at this Rob’s blog post introducing IResult and Coroutines, one year before Async CTP!

But now async/await stuff is going to be mainstream; does it mean that IResult and Coroutines are becoming useless?
My initial opinion is that they could be kept just for backward compatibility, but they turned out to be still quite important for some reasons.

Access to execution context

In several scenarios the action defined in the ViewModel requires to gain direct access to the view or to the element triggering the action itself.
The view instance could be obtained using GetView method on Screen class (or implementing IViewAware), but the triggering element instance is not that easy to reach.

Moreover, while in some cases using an “Model-View-Presenter-like” approach is the only viable solution, generally speaking ViewModels should not control the UI directly, nor deal with presentation concerns at all.

Custom IResult, instead, provides an easy access to a class called ActionExecutionContext, which contains all the objects involved into the action execution: the containing view, the triggering element, the ViewModel instance, the method called, etc.
So, while creating ad-hoc IResults for simple asynchronous service calls could be tedious, using custom, reusable IResult to isolate UI concerns is quite effective, and helps to clean out ViewModels code.

Testability

It’s entirely possible testing async code written using async/await, usually replacing real services with mocks. Yet, using an IEnumerable<IResult> in the ViewModel allows to just test the returned “sequence”, without actually executing each IResult.

Action execution “wrapping”

Caliburn.Micro can map (using conventions or an explicit DSL) an event occurring on the UI side to a method call on the ViewModel side.
As a consequence, it’s very easy to turn a simple void method into an asynchronous one (async void or async Task), and let Caliburn.Micro to start its execution.

I don’t recommend this approach, however: this way, indeed, the action execution (from CM’s point of view) completes as soon as a Task object is created, while the actual async method execution usually take longer. Also, exceptions thrown during execution may go unobserved (see http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx) because the Task object is not available anymore.

For these reasons I wrote a simple extension taking care of it; the strategy I chose is to customize CM’s action invocation code, in order to intercept async methods returning Tasks and wrap them into an IResult.
This way I could simply take advantage of the existing infrastructure; in particular, I can use the builtin notification of Coroutines completion (Coroutine.Complete static event), which also bring information about possible exception occurred.

Here the relevant code:

public static class AsyncAwaitSupport
{
	public static void Hook()
	{

		ActionMessage.InvokeAction = context =>
		{

			var values = MessageBinder.DetermineParameters(context, context.Method.GetParameters());
			var returnValue = context.Method.Invoke(context.Target, values);

			var task = returnValue as Task;
			if (task != null)
			{
				returnValue = new TaskResult(task);
			}

			var result = returnValue as IResult;
			if (result != null)
			{
				returnValue = new[] { result };
			}

			var enumerable = returnValue as IEnumerable;
			if (enumerable != null)
			{
				Coroutine.BeginExecute(enumerable.GetEnumerator(), context);
				return;
			}

			var enumerator = returnValue as IEnumerator;
			if (enumerator != null)
			{
				Coroutine.BeginExecute(enumerator, context);
				return;
			}
		};
	}

	private class TaskResult : IResult
	{
		Task task;
		public TaskResult(Task task)
		{
			if (task == null) throw new ArgumentNullException("task");
			this.task = task;
		}

		public event EventHandler Completed = delegate { };

		public void Execute(ActionExecutionContext context)
		{
			task.ContinueWith(t =>
			{
				Completed(this, new ResultCompletionEventArgs {
                                            WasCancelled = t.IsCanceled,
                                            Error = t.Exception }
                                       );
			});
		}
	}

}

The extension is hooked during the bootstrapper initialization:

protected override void Configure()
{
    base.Configure();

    AsyncAwaitSupport.Hook();

    //...
}

Once I have this in place, I could translate the following code (taken from CoroutineViewModel.cs inside Caliburn.Micro.WinRT.Sample):

public IEnumerable ExecuteCoroutine()
{
	yield return new VisualStateResult("Loading");
	yield return new DelayResult(2000);
	yield return new VisualStateResult("LoadingComplete");
	yield return new MessageDialogResult("This was executed from a custom IResult, MessageDialogResult.", "IResult Coroutines");
}

into something like this:

public async Task ExecuteTask()
{
	this.SetVisualStateOnView("Loading");

	await Task.Delay(2000);

	this.SetVisualStateOnView("LoadingComplete");

	//This is just a sample: I don't actually recommend calling UI code from here in real code.
	var dialog = new Windows.UI.Popups.MessageDialog("This was executed with a regular MessageDialog.", "Async/await");
	await dialog.ShowAsync();
}

[Here the same post in Italian language]

26
May
11

Model-first navigation in WP7 with Caliburn.Micro

I just saw Rob’s recent UriBuilder addition in Caliburn.Micro source; it really simplifies the configuration of the View Model of the page to open through the use of a fluent-style API:

navigationService.UriFor<PageTwoViewModel>()
 .WithParam(x => x.NumberOfTabs, 5)
 .Navigate();

Also, it brings WP7 navigation stuff (which is strongly page oriented) a step closer to a model-first approach.

I was recently struggling with an attempt to improve this very area.

My goal, however, was to navigate to a real View Model instance, with the aim to get exactly that instance bound to the new page.

The idea I had is very simple (apparently, even too simple, which makes me think that it could eventually have some problems on the long run):

  • in the calling VM, create and set up an instance of the destination VM;
  • put the VM instance into a slot of the Phone Service state;
  • navigate to a generic “hosting” page (HostPage);
  • HostPage has a corresponding HostPageViewModel based on Conductor<> class; CM already takes care of binding it to the new page when the navigation is completed.On initialization, the HostPageViewModel just activates whatever it finds in the Phone Service state slot used before;
  • the View corresponding to the destination ViewModel is resolved and composed as usual inside the HostPage.

Actually, this is very similar to the old-school-web-application trick of using the http session to pass objects between different pages.

Not very elegant, yet effective.

I built a simple proof of concept showing the idea and the trick seems to work (I wonder why I didn’t try it before…).

Here the key points:

//the hosting VM
public class HostPageViewModel : Conductor<object>
{
	public const string TARGET_VM_KEY = "cm-navigation-target-vm";

	IPhoneService phoneService;
	public HostPageViewModel(IPhoneService phoneService) {
		this.phoneService = phoneService;
	}

	protected override void OnInitialize()
	{
		base.OnInitialize();
		if (!phoneService.State.ContainsKey(TARGET_VM_KEY)) return;

		var targetVM = phoneService.State[TARGET_VM_KEY];
		phoneService.State.Remove(TARGET_VM_KEY);

		this.ActivateItem(targetVM);
	}
}
<!-- the hosting View -->
<phone:PhoneApplicationPage x:Class="Caliburn.Micro.HelloWP7.HostPage" ...> 
    <ContentControl Name="ActiveItem" 
        HorizontalContentAlignment="Stretch" 
        VerticalContentAlignment="Stretch" /> 
</phone:PhoneApplicationPage> 
//a convenience extension method
public static class NavigationExtension
{
	public static void NavigateTo(this INavigationService navigationService, object targetModel)
	{ 
		IoC.Get<IPhoneService>().State[HostPageViewModel.TARGET_VM_KEY] = targetModel;
		navigationService.UriFor<HostPageViewModel>().Navigate();
	}
}
//the usage
PageTwoViewModel model = ... //obtain a new instance of the destination VM
model.NumberOfTabs = 5; //sets the VM up
navigationService.NavigateTo(model);

It has the great advantage of being very similar to the usual way we deal with dialogs in Caliburn.Micro.

Also, it allows to inject into the destination VM any data already available in the context of the calling VM, thus saving an hit to remote services or the use of a global data cache.

23
Oct
10

A quick Caliburn.Micro tip: WebService result

UPDATE 2011/01/03

Fixed a couple bugs:

  • Missing exception handling on callback execution
  • Missing null check on Message property

Many thanks to Luca D’Angelo for pointing them out.



A user recently asked, on Caliburn.Micro forum, for an equivalent version of WebServiceResult, a convenience IResult built by Rob in the (full) Caliburn’s Contact Manager sample.

Porting was quite straightforward, despite the important difference in the framework internal structure.

Here is the class source:

 
using System; 
using System.Linq; 
using System.Linq.Expressions; 
public class WebServiceResult<T, K> : IResult 
	where T : new() 
	where K : EventArgs< 
{
	readonly static Func<bool> ALWAYS_FALSE_GUARD= () => false;
	readonly static Func<bool> ALWAYS_TRUE_GUARD = () => true;
	private readonly Action<K> _callback;
	private readonly Expression<Action<T>> _serviceCall;
	private ActionExecutionContext _currentContext;
	private Func<bool> _originalGuard;
	public WebServiceResult(Expression<Action<T>> serviceCall)
	{
		_serviceCall = serviceCall;
	}
	public WebServiceResult(Expression<Action<T>> serviceCall, Action<K> callback)
	{
		_serviceCall = serviceCall;
		_callback = callback;
	}
	public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
	public void Execute(ActionExecutionContext context)
	{
		_currentContext = context;
		//if you would to disable the control that caused the service to be called, you could do this:
		ChangeAvailability(false);
		var lambda = (LambdaExpression)_serviceCall;
		var methodCall = (MethodCallExpression)lambda.Body;
		var eventName = methodCall.Method.Name.Replace("Async", "Completed");
		var eventInfo = typeof(T).GetEvent(eventName);
		var service = new T();
		eventInfo.AddEventHandler(service, new EventHandler<K>(OnEvent));
		_serviceCall.Compile()(service);
	}
	public void OnEvent(object sender, K args)
	{
		//re-enable the control that caused the service to be called:
		ChangeAvailability(true);
                try {
		    if (_callback != null)
			_callback(args);
	    	    Completed(this, new ResultCompletionEventArgs());
                } catch (Exception ex) {
	    	    Completed(this, new ResultCompletionEventArgs{ Error = ex });
                }
	}
	private void ChangeAvailability(bool isAvailable)
	{
		if (_currentContext == null || _currentContext.Message == null) return;
		if (!isAvailable) {
			_originalGuard = _currentContext.CanExecute;
			_currentContext.CanExecute = ALWAYS_FALSE_GUARD;
		}
		else if (_currentContext.CanExecute == ALWAYS_FALSE_GUARD) {
			_currentContext.CanExecute = _originalGuard ?? ALWAYS_TRUE_GUARD;
		}
		_currentContext.Message.UpdateAvailability();
	}
}

The invocation:

//in the service code
public class MyService
{
	[OperationContract]
	public int DoWork(int value)
	{
		return new Random().Next();
	}
}
//in the calling ViewModel code
public IEnumerable<IResult> CallService()
{
	int theParameter = 0;
	yield return new WebServiceResult<MyServiceClient, DoWorkCompletedEventArgs>(
		x => x.DoWorkAsync(theParameter),
		x => UseResultElsewhere(x.Result)
	);
}
10
Aug
10

A Caliburn.Micro recipe: filters

Caliburn.Micro is a lightweight implementation of most core Caliburn feaures in a small assembly. Rob did a great work keeping footprint small, but something was necessarily left over.

One of the features missing in Caliburn.Micro is filters, a set of action decorations aimed to provide additional behaviors to a particular action. Filters bring two major advantages:

  • they help to keep View Model free from noisy code not concerning its main purpose (thus simplifying its unit testing, too);
  • they allow to share the implementation of cross-cutting concerns between different View Models, moving it into infrastructure components.

Since I use filters quite often, I wanted to provide an implementation for Caliburn.Micro, too.

I could have ported it from Caliburn just fixing some slightly changed signature; yet, in the spirit of keeping things small and simple, I decided to use a less fine-grained design. Plus, I wanted to proof a design idea going through my mind from a while.
Basically, I noted a similarity in the hook point provided by Caliburn infrastructure to filters and IResults (coroutines), so I would check if the two concepts could be unified; this would have allowed to include filters in Caliburn.Micro with little additional infrastructure.

Design

Filters falls into two main categories, depending on the mechanism used to interact with the target action: IExecutionWrapper and IContextAware.

The common IFilter interface is little more than a marker and just defines a Priority property aimed to trim the filter application order:

public interface IFilter {
	int Priority { get; }
}

IExecutionWrapper

Most filters capability are built around AOP concepts and works through the interception of an action execution: doing this allows to add operation before and after the action execution itself or something more complex like dispatching the action in another thread.
Coroutine infrastructure already has this capability, so a filter willing to intercept an action can simply wrap the original execution into a “wrapping” IResult:

public interface IExecutionWrapper : IFilter {
	IResult Wrap(IResult inner);
}

To enable filter hooking, I had to replace the core invocation method of ActionMessage:

//in bootstrapper code:
ActionMessage.InvokeAction = FilterFrameworkCoreCustomization.InvokeAction;
...

public static class FilterFrameworkCoreCustomization
{
    ...
    public static void InvokeAction(ActionExecutionContext context)
    {
        var values = MessageBinder.DetermineParameters(context, context.Method.GetParameters());
        IResult result = new ExecuteActionResult(values);
        var wrappers = FilterManager.GetFiltersFor(context).OfType<IExecutionWrapper>();
        var pipeline = result.WrapWith(wrappers);

        //if pipeline has error, action execution should throw!
        pipeline.Completed += (o, e) =>
        {
            Execute.OnUIThread(() =>
            {
                if (e.Error != null) throw new Exception(
                    string.Format("An error occurred while executing {0}", context.Message),
                    e.Error
                );
            });
        };

        pipeline.Execute(context);
    }
    ...
}

Every action is actually executed within an ExecuteActionResult (code omitted here) that deals with simple action as well as coroutines, uniforming all of them to a common IResult interface.

This “core” IResult is afterwards wrapped over and over by each filter attached to the action and finally executed.

Let’s have a look at FilterManager:

public static class FilterManager
{

    public static IResult WrapWith(this IResult inner, IEnumerable<IExecutionWrapper> wrappers)
    {
        IResult previous = inner;
        foreach (var wrapper in wrappers)
        {
            previous = wrapper.Wrap(previous);
        }
        return previous;
    }

    public static Func<ActionExecutionContext, IEnumerable<IFilter>> GetFiltersFor = (context) => {
        return context.Target.GetType().GetAttributes<IFilter>(true)
                .Union(context.Method.GetAttributes<IFilter>(true))
                .OrderBy(x => x.Priority);
    };
}

Note that the GetFiltersFor method is replaceable to allow for another filter lookup strategy (for example, based on convention or external configuration instead of attributes).

IContextAware

While IExecutionWrapper-s does their work during the action execution, the other filter category, IContextAware, operates when action is not executing, providing preconditions for execution (the related predicate is held by ActionExecutionContext) or observing the ViewModel to force an update of the action availability:

public interface IContextAware : IFilter, IDisposable
{
	void MakeAwareOf(ActionExecutionContext context);
}

Filters implementing this interface are given a chance, during ActionMessage initialization, to hook the execution context; to achieve this, I had to slightly tweak the ActionMessage again:

//in bootstrapper code:
var oldPrepareContext = ActionMessage.PrepareContext;
ActionMessage.PrepareContext = context =>
{
    oldPrepareContext(context);
    FilterFrameworkCoreCustomization.PrepareContext(context);
};
...

public static class FilterFrameworkCoreCustomization
{
    ...
    public static void PrepareContext(ActionExecutionContext context)
    {
        var contextAwareFilters = FilterManager.GetFiltersFor(context).OfType<IContextAware>()
            .ToArray();
        contextAwareFilters.Apply(x => x.MakeAwareOf(context));

        context.Message.Detaching += (o, e) =>
        {
            contextAwareFilters.Apply(x => x.Dispose());
        };
    }
    ...
}

Implementing filters

To simplify filters construction, I made a base class for IExecutionWrapper which includes all the boilerplate code and provides some standard  customization point:

public abstract class ExecutionWrapperBase : Attribute, IExecutionWrapper, IResult
{
    public int Priority { get; set; }

    /// <summary>
    /// Check prerequisites
    /// </summary>
    protected virtual bool CanExecute(ActionExecutionContext context) { return true;}
    /// <summary>
    /// Called just before execution (if prerequisites are met)
    /// </summary>
    protected virtual void BeforeExecute(ActionExecutionContext context) { }
    /// <summary>
    /// Called after execution (if prerequisites are met)
    /// </summary>
    protected virtual void AfterExecute(ActionExecutionContext context) { }
    /// <summary>
    /// Allows to customize the dispatch of the execution
    /// </summary>
    protected virtual void Execute(IResult inner, ActionExecutionContext context)
    {
        inner.Execute(context);
    }
    /// <summary>
    /// Called when an exception was thrown during the action execution
    /// </summary>
    protected virtual bool HandleException(ActionExecutionContext context, Exception ex) { return false; }

    IResult _inner;
    IResult IExecutionWrapper.Wrap(IResult inner)
    {
        _inner = inner;
        return this;
    }

    void IResult.Execute(ActionExecutionContext context)
    {
        if (!CanExecute(context))
        {
            _completedEvent.Invoke(this, new ResultCompletionEventArgs { WasCancelled = true });
            return;
        }

        try
        {

            EventHandler<ResultCompletionEventArgs> onCompletion = null;
            onCompletion = (o, e) =>
            {
                _inner.Completed -= onCompletion;
                AfterExecute(context);
                FinalizeExecution(context, e.WasCancelled, e.Error);
            };
            _inner.Completed += onCompletion;

            BeforeExecute(context);
            Execute(_inner, context);

        }
        catch (Exception ex)
        {
            FinalizeExecution(context, false, ex);
        }
    }

    void FinalizeExecution(ActionExecutionContext context, bool wasCancelled, Exception ex)
    {
        if (ex != null && HandleException(context, ex))
            ex = null;

        _completedEvent.Invoke(this, new ResultCompletionEventArgs { WasCancelled = wasCancelled, Error = ex });
    }

    event EventHandler<ResultCompletionEventArgs> _completedEvent = delegate { };
    event EventHandler<ResultCompletionEventArgs> IResult.Completed
    {
        add { _completedEvent += value; }
        remove { _completedEvent -= value; }
    }
}

Finally I could reproduce the behavior of some well known Caliburn filters in Caliburn.Micro:

/// <summary>
/// Provides asynchronous execution of the action in a background thread
/// </summary>
public class AsyncAttribute : ExecutionWrapperBase
{
    protected override void Execute(IResult inner, ActionExecutionContext context)
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            inner.Execute(context);
        });
    }

}
//usage:
//[Async]
//public void MyAction() { ... }

/// <summary>
/// Allows to specify a "rescue" method to handle exception occurred during execution
/// </summary>
public class RescueAttribute : ExecutionWrapperBase
{

    public RescueAttribute() : this("Rescue") { }
    public RescueAttribute(string methodName)
    {
        MethodName = methodName;
    }

    public string MethodName { get; private set; }

    protected override bool HandleException(ActionExecutionContext context, Exception ex)
    {
        var method = context.Target.GetType().GetMethod(MethodName, new[] { typeof(Exception) });
        if (method == null) return false;

        try
        {
            var result = method.Invoke(context.Target, new object[] { ex });
            if (result is bool)
                return (bool)result;
            else
                return true;
        }
        catch
        {
            return false;
        }
    }
}
//usage:
//[Rescue]
//public void ThrowingAction()
//{
//    throw new NotImplementedException();
//}
//public bool Rescue(Exception ex)
//{
//    MessageBox.Show(ex.ToString());
//    return true;
//}

/// <summary>
/// Sets "IsBusy" property to true (on models implementing ICanBeBusy) during the execution
/// </summary>
public class SetBusyAttribute : ExecutionWrapperBase
{
    protected override void BeforeExecute(ActionExecutionContext context)
    {
        SetBusy(context.Target as ICanBeBusy, true);
    }
    protected override void AfterExecute(ActionExecutionContext context)
    {
        SetBusy(context.Target as ICanBeBusy, false);
    }
    protected override bool HandleException(ActionExecutionContext context, Exception ex)
    {
        SetBusy(context.Target as ICanBeBusy, false);
        return false;
    }

    private void SetBusy(ICanBeBusy model, bool isBusy)
    {
        if (model != null)
            model.IsBusy = isBusy;
    }

}
//usage:
//[SetBusy]
//[Async] //prevents UI freezing, thus allowing busy state representation
//public void VeryLongAction() { ... }

/// <summary>
/// Updates the availability of the action (thus updating the UI)
/// </summary>
public class DependenciesAttribute : Attribute, IContextAware
{

    ActionExecutionContext _context;
    INotifyPropertyChanged _inpc;

    public DependenciesAttribute(params string[] propertyNames)
    {
        PropertyNames = propertyNames ?? new string[] { };
    }

    public string[] PropertyNames { get; private set; }
    public int Priority { get; set; }

    public void MakeAwareOf(ActionExecutionContext context)
    {
        _context = context;
        _inpc = context.Target as INotifyPropertyChanged;
        if (_inpc != null)
            _inpc.PropertyChanged += inpc_PropertyChanged;
    }

    public void Dispose()
    {
        if (_inpc != null)
            _inpc.PropertyChanged -= inpc_PropertyChanged;
        _inpc = null;
    }

    void inpc_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (PropertyNames.Contains(e.PropertyName))
        {
            Execute.OnUIThread(() =>
            {
                _context.Message.UpdateAvailability();
            });
        }
    }
}
//usage:
//[Dependencies("MyProperty", "MyOtherProperty")]
//public void DoAction() { ... }
//public bool CanDoAction() { return MyProperty > 0 && MyOtherProperty < 1; }

/// <summary>
/// Allows to specify a guard method or property with an arbitrary name
/// </summary>
public class PreviewAttribute : Attribute, IContextAware
{

    public PreviewAttribute(string methodName)
    {
        MethodName = methodName;
    }

    public string MethodName { get; private set; }
    public int Priority { get; set; }

    public void MakeAwareOf(ActionExecutionContext context)
    {
        var targetType = context.Target.GetType();
        var guard = targetType.GetMethod(MethodName);
        if (guard== null)
            guard = targetType.GetMethod("get_" + MethodName);

        if (guard == null) return;

        var oldCanExecute = context.CanExecute;
        context.CanExecute = () =>
        {
            if (!oldCanExecute()) return false;

            return (bool)guard.Invoke(
                context.Target,
                MessageBinder.DetermineParameters(context, guard.GetParameters())
            );
        };
    }

    public void Dispose() { }

}
//usage:
//[Preview("IsMyActionAvailable")]
//public void MyAction(int value) { ... }
//public bool IsMyActionAvailable(int value) { ... }

Source code is here: https://hg01.codeplex.com/forks/marcoamendola/caliburnmicromarcoamendolafork

21
May
10

DomusDotNet: a new italian .NET community based in Rome

logo_no_pl

Last friday, during the Visual Studio 2010 Community Tour – Rome, we presented DomusDotNet, a new italian .NET community based in Rome.

Our wish is to create a place to share informations, news, technical experiences and meet (both in the web and in the "real" world) other people interested in professional improvement.

If you speak Italian, please have a look at our website www.domusdotnet.org; we have been working hard (and late into the night…) to complete the beta version on schedule, so please don’t be too severe upon it!

Comments and suggestions are welcome!

(some of) DomusDotNet members

01
Mar
10

Binding to an interface property with WPF

I received a tweet from José in reply to my comment about the ugliness of the WPF syntax to bind to an interface property, when the interface is explicitly implemented.

He reminded me that I tried very hard -and uselessly- to find a workaround for this, and one day the correct syntax just popped out from a blog I was reading (I can’t remember who the benefactor was… I would have liked to thank him here).

Now, the ill-famed syntax:

{Binding Path=(myns:IMyInterface.MyProperty)}

25
Jan
10

Migration to Caliburn v2

After being held captive by the aliens for the last couple months… ehm… what? it’s hardly credible?

The plain truth is that I’m having a very busy period at work, so I had to cut hobbies to avoid my daughter stopping calling me “papà” (“daddy”, in italian).

Nevertheless, I missed the pleasure of writing something here (I have to exercise my English!) so I’ll report about my effort to port a couple project I’m working on to Caliburn v2. I froze my references to v1.x branch some time ago and deferred the update until today, for the lack of necessary time and because I was afraid to slow down my work with breaking changes.
But all new stuff introduced by Rob in Caliburn trunk was too tempting; plus, I started to be hardly able to follow new Caliburn forum posts about new features, so I finally decided to switch.

It was not hard at all, I have to say.

I still have to refine class and variables names to follow the new “Screens” naming (formerly “Presenters”), but I’ve got the whole thing compiling and passing tests.

For those who are about to start the migration to v2, here is a list of the changes I had to make in my projects. It’s very unlikely an exhaustive list of all changes made to Caliburn, but, since I use a lot of customizations, I think it covers the most frequent issues.

Modules

Modules structure was a bit refactored to simplify (I guess) the addiction of configuration parameters to existing and new framework modules; from the user perspective, it means just to choose the new correct base class and the renaming of a couple methods.
I use modules for two distinct goals:

  • provide a way for independent application parts to load and hook itself into the shell without hard-wiring their reference in the main exe
  • hook some app-specific configuration into the CaliburnFramework static accessor, mimicking other Caliburn’s modules fluent configuration style.

For the first need I inherited from ModuleBase; for the second goal, I started from CaliburnModule<T> and added an extension method to hook into the fluent configuration. In both cases I had to change the name of Initialize and GetComponents methods into InitializeCore and GetComponentsCore.


public class MyAppModule : ModuleBase
{
  protected override void InitializeCore(Microsoft.Practices.ServiceLocation.IServiceLocator locator)
  {
  }

  protected override IEnumerable<IComponentRegistration> GetComponentsCore()
  {
    yield return Singleton(typeof(IMyService), typeof(MyService));
  }
}
 

public class MyFluentModuleConfig : CaliburnModule<MyFluentModuleConfig>
{
  protected override void InitializeCore(Microsoft.Practices.ServiceLocation.IServiceLocator locator)
  {

  }

  protected override IEnumerable<IComponentRegistration> GetComponentsCore()
  {
    yield return Singleton(typeof(IMyOtherService), typeof(MyOtherService));
  }
}

public static class Extenstions
{

  public static MyFluentModuleConfig MyFluentModule(this IModuleHook hook)
  {
    return hook.Module(MyFluentModuleConfig.Instance);
  }
}

Screens

There was a major refactoring in this area:

  • Naming change (“Presenters” are now “Screens”)
  • Strong typing of the screen with regard to their “subject”
  • Strong typing of screen composites based on contained screen
  • Automatic activation of already opened screen based on their subject

In order to accomodate existing code to v2 you have to replace existing class and interfaces following the subsequent scheme:

  • IPresenter –> IScreen
  • IPresenterHost -> IScreenCollection<IScreen>
  • IPresenterManager -> IScreenConductor<IScreen>
  • MultiPresenter -> ScreenConductor<IScreen>.WithCollection.AllScreensActive
  • MultiPresenterManager -> ScreenConductor<IScreen>.WithCollection.OneScreenActive
  • PresenterManager -> ScreenConductor<IScreen>
  • Presenter –> Screen

I closed generic class against IScreen to exactly reproduce the previous behaviour; note that I also chose to use generic version of IScreenCollection and IScreenConductor to match the new signature of screen-related extension functions.

I’m still not getting the benefits of strong typing and subject management, I have to refactor my applications in more depth for this.

Application

I had to fix a trivial signature change in CaliburnApplication.ConfigurePresentationFramework override. In addiction, I changed calls to Presentation Framework module configuration:

module.UsingViewStrategy<NoxaViewStrategy>();

module.UsingWindowManager<NoxaWindowManager>();

changed to

module.Using(c => c.ViewLocator<NoxaViewStrategy>());

module.Using(c => c.WindowManager<NoxaWindowManager>());

Framework classes

Some refactoring was done in the framework internals, so you only have to adjust this classes if you have done customization of the framework behaviour:

  • IWindowManager interface: bool isDialog parameter was added in EnsureWindow signature
  • IResult: signature of Execute method is changed from
    void Execute(IRoutedMessageWithOutcome message, IInteractionNode handlingNode)
    to
    void Execute(ResultExecutionContext context)
  • IResult: signature of Completed event was changed in
    EventHandler<ResultCompletionEventArgs>

Other random member name changes

  • RoutedMessageController -> DefaultRoutedMessageController
  • IBinder -> IViewModelBinder
  • ViewAttribute: namespace change
  • IViewStrategy -> IViewLocator
  • IViewStrategy.GetView –> IViewLocator.Locate
  • CurrentPresenter property -> ActiveScreen
  • Presenters property -> Screens
  • IExtendedPresenter interface -> IScreenEx
  • Open method -> OpenScreen
  • Shutdown method -> ShutdownScreen



August 2014
S M T W T F S
« Oct    
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: