Refactoring SignalR Feedback for Cross Platform with AutoFac and CommonServiceLocator for Simple Dependency Injection

At this point I’m starting to think about “real application” problems such as how services/components/view models are located and how navigation between view models is going to work. For anyone following this series of posts I currently have Universal (Windows/Windows Phone), Xamarin Forms (iOS (broken), Android and Windows Phone 8.0) and WPF clients. So far I’ve been covering how to handle accessing data (Mobile Services) and authentication (Azure Active Directory) and to a lesser degree data binding (XAML, Data binding and View Models) but I haven’t looked at how the client applications are going to be architected. There are some core issues which need to be solved in a general way so that it will work across all client applications, such as how view models are located (created and supplied to corresponding view) and how navigation can be triggered within a view model that will correlate to a page/view change in the UI layer. This issue I’ll leave to another day as it generally involves either picking an existing framework (MvvmCross, Mvvmlight etc) or rolling your own – and I’m still undecided on what to do.

What I do know is that in order to refactor my SignalR implementation, which is currently done within the WPF Window codebehind, so that it will work on all platforms I’m going to need to define a set of interfaces in the Core library (PCL) which is implemented by each platform. I should be able to reuse the same code on all platforms for the implementation so the code can go into the Shared.Client project – it just needs to reference the platform specific implementation of SignalR.

Before we get onto the refactoring, I’m going to add a couple of NuGet packages which will give use an IoC that we can call on to resolve dependencies. I was initially going to jump forward and include MvvmLight but I went looking for the navigation support and was a little disappointed – I’ll come back and do a full evaluation when I get to dealing with navigation but for now I’ve opted to go for Autofac. However, just in case I want to change IoC provider at a later stage I’ve also opted to include the CommonServiceLocator so as to provide a bit of an abstraction from any given library when it comes to resolving dependencies.

I’ve added the following NuGet packages to the Core and all client projects

Autofac

CommonServiceLocator

Autofac Extras: Microsoft Common Service Locator

Both Autofac and the CommonServiceLocator require logic to be run on application startup. There is both platform independent and platform specific code to be run. For this reason I’ve added the ClientApplicationCore class to the Shared.Client project and the ApplicationCore class to the Core library, with the following implementation:

public class ClientApplicationCore
{
    public static ClientApplicationCore Default { get; set; }

    static ClientApplicationCore()
    {
        Default=new ClientApplicationCore();
    }

    public ClientApplicationCore()
    {
        CoreApplication=new ApplicationCore();
    }

    public ApplicationCore CoreApplication { get; set; }

    public void ApplicationStartup()
    {
        CoreApplication.Startup(builder => builder.RegisterType<SignalRFactory>().As<ISignalR>());
    }
}

 

public class ApplicationCore
{
    public void Startup(Action<ContainerBuilder> dependencyBuilder)
    {
        var builder = new ContainerBuilder();
        dependencyBuilder(builder);
        // Perform registrations and build the container.
        var container = builder.Build();

        // Set the service locator to an AutofacServiceLocator.
        var csl = new AutofacServiceLocator(container);
        ServiceLocator.SetLocatorProvider(() => csl);
    }
}

You’ll notice that at this stage all I’m registering is SignalRFactory as an implementation of the ISignalR interface. This interface is one of two that I’ve defined within the Core library to assist with the refactored SignalR support:

public interface ISignalR
{
    Task<ICommunicationHub> Connect<THub>(IMobileServiceClient mobileService);
}

public interface ICommunicationHub:IDisposable
{
    string ConnectionId { get; }

    IDisposable Register<TMessageType>(string eventName, Action<TMessageType> handler);
}

The implementation is placed in the Shared.Client project so that it is reused across all client platforms:

internal class SignalRFactory : ISignalR
{
    public async Task<ICommunicationHub> Connect<THub>(IMobileServiceClient mobileService)
    {
        var hubConnection = new HubConnection(MainViewModel.MobileService.ApplicationUri.AbsoluteUri);

        hubConnection.Headers[“x-zumo-application”] = MainViewModel.MobileService.ApplicationKey;

        IHubProxy proxy = hubConnection.CreateHubProxy(typeof(THub).Name);
        await hubConnection.Start();

        return new CommunicationHub { Connection = hubConnection, Hub = proxy };
    }
}

internal class CommunicationHub : ICommunicationHub
{
    public string ConnectionId { get { return Connection.ConnectionId; } }
    public HubConnection Connection { get; set; }
    public IHubProxy Hub { get; set; }
    public IDisposable Register<TMessageType>(string eventName, Action<TMessageType> handler)
    {
        return Hub.On(eventName, handler);
    }

    public void Dispose()
    {
        if (Connection != null)
        {
            Connection.Dispose();
            Connection = null;
            Hub = null;
        }

    }
}

The implementation of the GenerateReport method on the MainViewModel can now be refactored to look up the ISignalR implementation:

public async void GenerateReport()
{
    string message;
    try
    {
        var signalr = ServiceLocator.Current.GetInstance<ISignalR>();
        var hub= await signalr.Connect<LongRunningFeedbackHub>(MobileService);
        hub.Register<string>(“Progress”, msg => Debug.WriteLine(msg));

        var result = await MobileService.InvokeApiAsync<string>(“Reporter”, HttpMethod.Get, new Dictionary<string, string>{{“id”, hub.ConnectionId}});
        message = result;
    }
    catch (MobileServiceInvalidOperationException ex)
    {
        message = ex.Message;
    }
    Debug.WriteLine(message);
}

And that’s it – this should work without modification on each of the client platforms….

Leave a comment