Reusable WCF Service Data Access Layer for WP7, MonoTouch and Mono for Android

This is a quick overview of experimentation with creating a reusable WCF Service Data Access Layer for Cross-Platform Mobile Development effort. For more details see my article here.

The Challenge

Build WCF Data Services based data access layer that can be reused by MonoTouch, Mono for Android and WP7 applications.

The Solution

Silverlight 4 proxy classes for WCF Data Services generated with “noconfig” option  work in Wp7, MonoTouch and Mono for Android projects. Only authenticated callers can access those services thus each call to the data service needs to pass along an authentication cookie. The Login Manager class serves the purpose of retrieving  the cookie from the authentication service and passing it around during subsequent calls to data services.

using System;
using System.Net;
using System.ServiceModel;

namespace DataRepository
{
    public class LoginManager
    {
        public CookieContainer CookieJar { get; set; }
        private AuthenticationServiceClient _remotePortal;

        public void LogUserIn(
            CallResult<bool> callback
            , string userName
            , string password)
        {
            var binding = new BasicHttpBinding();
            binding.EnableHttpCookieContainer = true;

            _remotePortal = new AuthenticationServiceClient(binding,
                 new EndpointAddress(
                     "http://xx.xx.xx/app/AuthService.svc"));

            _remotePortal.LoginCompleted +=
                new EventHandler<LoginCompletedEventArgs>
                    (RemotePortal_LoginCompleted);

            _remotePortal.LoginAsync(
                userName, password, null, true, callback);
        }

        void RemotePortal_LoginCompleted(
            object sender, LoginCompletedEventArgs e)
        {
            var callback = e.UserState as CallResult<bool>;

            if (e.Error != null)
            {
                callback.Notify(e.Error);
            }
            else
            {
                CookieJar = _remotePortal.CookieContainer;
                callback.Notify(e.Result);
            }
        }
    }
}

 

An example Data Repository Class is presented below. The class uses the authentication cookie to access data services.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.ServiceModel;

namespace DataRepository
{
    public class LocationRepository
    {
        private ClientDataService2Client _remotePortal;

        public LocationRepository() { }

        public void GetLocations(
            CallResult<List<Location>> callback
            , CookieContainer cookieContainer)
        {
            var binding = new BasicHttpBinding();
            binding.EnableHttpCookieContainer = true;

            _remotePortal = new ClientDataService2Client(binding,
                new EndpointAddress(
                    "http://xx.xx.xx/app/myservice.svc"));

            _remotePortal.CookieContainer = cookieContainer;

            _remotePortal.GetLocationsCompleted +=
                new EventHandler<GetLocationsCompletedEventArgs>(
                    RemotePortal_GetLocationsCompleted);

            _remotePortal.GetLocationsAsync(callback);

        }

        void RemotePortal_GetLocationsCompleted(
            object sender
            , GetLocationsCompletedEventArgs e)
        {
              var callback = e.UserState as CallResult<List<Location>>;

            if (e.Error != null)
            {
                callback.Notify(e.Error);
            }
            else
            {
                var result = e.Result;
                var locations = e.Result;
                callback.Notify(locations.ToList());
            }
        }
    }
}

 

“CallResult” is a class presented in Visual Studio Magazine article by Benjamin Day. He describes the ways of dealing with asynchronous service calls in Silverlight and because I choose to use Silverlight Proxy Classes I borrowed the design presented in the article because it suited my purpose very well.

And the final example of MonoTouch UITableViewContoller class that uses the service as its data source. The “red” highlighted code is due to opening a project on the computer without MonoTouch installed. All the classes presented here are from real working project.

using MonoTouch.UIKit;
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using DataRepository;

namespace Cgb.Mobile.UX
{
    partial class RootViewController : UITableViewController
    {
        private List<Location> _groups;
        private static LoginManager _loginManager;
        private bool _isLoggedIn;

        public RootViewController(IntPtr handle)
            : base(handle)
        {
            var x = 1;
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            Title = "Locations";

            //it will be comming from stored values
            //(saved during the last successfull run
            //of the application)
            _groups = new List<Location>
                { new Location
                      {
                          Id = 1, Name = "Stored Loc 1"
                      },
                  new Location { Id = 2, Name = "Stored Loc 2" } };
            _loginManager = new LoginManager();
            _loginManager.LogUserIn(new CallResult<bool>(LoggedIn),
                "userName", "password");

            this.TableView.Source = new DataSource(this, _groups);
        }

        private void LoggedIn(CallResult<bool> callback)
        {
            if (callback.Error != null)
            {
                var x = callback.Error.Message;
            }
            else
            {
                _isLoggedIn = callback.Result;

                var repository = new LocationRepository();
                repository.GetLocations(
                    new CallResult<List<Location>>(
                        LocationsReturned)
                    , _loginManager.CookieJar);
            }
        }

        private void LocationsReturned(
            CallResult<List<Location>> callback)
        {
            if (callback.Error != null)
            {
                var x = callback.Error.Message;
            }
            else
            {
                var locations = callback.Result;

                _groups = locations;

                this.TableView.Source = new DataSource(this, _groups);

                InvokeOnMainThread(() => TableView.ReloadData());
            }
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
        }

        public override void ViewDidUnload()
        {
            base.ViewDidUnload();
        }

        class DataSource : UITableViewSource
        {
            RootViewController controller;

            List<Location> rows = new List<Location>();

            public DataSource(RootViewController controller,
                List<Location> data)
            {
                this.controller = controller;
                rows = data;
            }

            public override int NumberOfSections(UITableView tableView)
            {
                return 1;
            }
            // Customize the number of rows in the table view
            public override int RowsInSection(UITableView tableview
                , int section)
            {
                return rows.Count;
            }

            // Customize the appearance of table view cells.
            public override UITableViewCell GetCell(
                UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
            {
                string cellIdentifier = "Cell";
                var cell = tableView.DequeueReusableCell(cellIdentifier);
                if (cell == null)
                {
                    cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
                }
                cell.TextLabel.Text = rows[indexPath.Row].Name;
                return cell;
            }

            // Override to support row selection in the table view.
            public override void RowSelected(
                UITableView tableView
                , MonoTouch.Foundation.NSIndexPath indexPath)
            {
                var commoditiesViewController =
                    new CommoditiesTableViewController(
                        _loginManager.CookieJar, rows[indexPath.Row].Id);

                commoditiesViewController.Title = rows[indexPath.Row].Name;

                controller.NavigationController.PushViewController(
                    commoditiesViewController, true);

                tableView.DeselectRow(indexPath, true);
            }
        }
    }

}

 

The layout of the projects in Visual Studio.

IPhoneApp

iPhone Application Project

DroidApp

Android Application Project

WP7App

Windows Phone 7 Project

This entry was posted in Mono, Mono for Android, MonoTouch, WCF Data Services, WP7 and tagged , , , , . Bookmark the permalink.

Comments are closed.