Click here to Skip to main content
15,884,058 members
Articles / General Programming

A framework for building of WP7 application

Rate me:
Please Sign up or sign in to vote.
4.90/5 (15 votes)
11 Feb 2012CPOL12 min read 50.4K   42   18
This article describes the key principles of PhoneCore Framework implementation and how to use it for building Windows Phone 7 application.

Table of content

  • Introduction
  • Overview
    • Configuration
      • Go deeper
    • Diagnostic
      • Tracing
      • Usage of tracing
    • Dependency injection container
      • Aspect-oriented programming support
    • Bootstrapping
      • ApplicationBootstrapper class
      • BootsrapperService
      • Core plugin
      • Custom plugins
    • Navigation
      • Interfaces
      • Implementation
    • Miscellaneous
  • The example of usage: SecureBox application
    • Setting up framework
    • Business layer
    • Data layer
      • Database schema
      • Initialization
    • Presentation layer
      • View
      • ViewModel
    • Unit testing
  • Conclusion
  • Points of interest

Introduction

PhoneCore Framework is a framework which helps to build applications on Windows Phone 7. It provides:
  • Navigation engine which simplifies the navigation between pages
  • Dependency injection container which helps to create loosely-coupled applications
  • Aspect-oriented programming support by custom method interceptors using custom proxy class generated at compile time (from version 0.6.1)
  • Configuration subsystem which allows to manage workflow without rebuilding of an application
  • Tracing engine which allows to do postmortem analysis or analyze workflow/performance
  • Bootstrapping engine with plugin architecture
  • Primitive types: Lazy, Tuple

Presentation1.png

UPDATE: Since the 0.6.1 release, the framework provides the subset of Aspect-oriented programming features by intercepting of method calls. AOP engine includes:

  • proxy generation at compile time by special tool using configuration information for types which implement some interface
  • custom interceptors for methods of these types

Container changes and improvements:

  • RegisterInstance method that registers existing object instance
  • Object lifetime managers
  • RegisterXXX/Resolve by name
  • Fluent interface for RegisterXXX methods

These changes aren't described here in details. Please visit official project site for the latest information:

Overview

Configuration

Configuration is the heart of the framework code and provides the way to:
  • control of application executing flow
  • create loosely-coupled classes
  • support plugin architecture
  • simplify unit testing
Let`s have a look at:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
  <system>
    <container type="PhoneCore.Framework.Container,PhoneCore.Framework">
      <!--<types>
        <type type="" mapTo="" />
      </types>-->
    </container>
    <!-- bootstrapping -->
    <bootstrapping>
      <bootstrappers>
        <bootstrapper name="Core" type="PhoneCore.Framework.Bootstrap.CoreBootstrapperPlugin, PhoneCore.Framework">
          <services>
            <navigation type="PhoneCore.Framework.Navigation.NavigationService, PhoneCore.Framework" />
            <fileSystem type="PhoneCore.Framework.Storage.IsolatedStorageFileService, PhoneCore.Framework" />
            <settings type="PhoneCore.Framework.Storage.IsolatedStorageSettingService, PhoneCore.Framework" />
          </services>
        </bootstrapper>
        <bootstrapper name="Init" type="SecureBox.UI.Infrastructure.Plugins.InitPlugin, SecureBox.UI" />
        ...
      </bootstrappers>
    </bootstrapping>
    <navigation>
      <service />
    </navigation>
    <diagnostic>
      <!-- trace subsystem-->
      <traces>
        <trace name="default" type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTrace, PhoneCore.Framework">
          <storage connectionString="Data Source=isostore:/tracedb.sdf">
            <init>
              ....
            </init>
          </storage>
          <level>0</level>
          <controller type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTraceController, PhoneCore.Framework" 
                      template="Resources/Templates/DefaultTraceReport.txt" />
        </trace>
      </traces>
    </diagnostic>
  </system>
  <data>
    <!-- data storage -->
    <storage connectionString="Data Source=isostore:/secureboxdb.sdf">
      <init>
        <!-- predefined templates -->
        <templates>
          <template name="Password" imageUrl="/Resources/Images/Templates/Password.png" />
          <template name="Account" imageUrl="/Resources/Images/Templates/Account.png" />
          <template name="Email" imageUrl="/Resources/Images/Templates/Email.png" />
          <template name="Note" imageUrl="/Resources/Images/Templates/Note.png" />
        </templates>
      </init>
    </storage>
  </data>
  <views>
    <!-- page mapping -->
    <pageMapping>
      <resolver type="SecureBox.UI.Infrastructure.PageResolver, SecureBox.UI" />
      <pages>
        <page name="Startup">
          <uri type="relative" address="/ViewPage/StartupViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.StartupViewPageModel, SecureBox.UI" />
        </page>
        <page name="Library">
          <uri type="relative" address="/ViewPage/LibraryViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.LibraryViewPageModel, SecureBox.UI" />
        </page>
        ....
      </pages>
    </pageMapping>
  </views>
</settings> 
As you can see, the configuration is stored as xml file in which there are defined the settings of different subsystems such as:
  • system/container – configures the settings of dependency injection container, attribute name specify the type of using container
  • system/bootstrapping - defines the tasks for bootstrapper service which should be run only once, e.g. :
    • register types in container
    • initialize page mapping
  • system/diagnostic/tracing - defines the tracing subsystem
  • views/pageMapping – defines mapping between pages and its view models
User can define custom settings and easily access them into the code using ConfigSettings members described in details below.

Go deeper

At the moment, the configuration subsystem consists of the following classes:
  • ConfigSettings – entry point to the configuration. It is a singleton instance:
    namespace PhoneCore.Framework.Configuration
    {
        /// <summary>
        /// Provides the access to the configuration subsystem
        /// </summary>
        public class ConfigSettings
        {
    
            private ConfigElement _root;
    
            private ConfigSettings()
            {
                var document = XDocument.Load(ConfigFileName);
                _root = new ConfigElement(document.Root);
            }
    
            private const string ConfigFileName = "application.config";
    
            #region singleton
            private static object _syncLock = new object();
            private volatile static ConfigSettings _instance;
            public static ConfigSettings Instance
            {
                get
                {
                    if (_instance == null)
                        lock (_syncLock)
                            if (_instance == null)
                                _instance = new ConfigSettings();
                    return _instance;
                }
            }
            #endregion
    
            /// <summary>
            /// Get section
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public ConfigSection GetSection(string xpath)
            {
    
                return (new ConfigSection(_root)).GetSection(xpath);
            }
    
            /// <summary>
            /// Get the set of sections
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public IEnumerable<ConfigSection> GetSections(string xpath)
            {
                return (new ConfigSection(_root)).GetSections(xpath);
            }
    
        }
    } 
    As seen from above, it tries to use application.config file as storage
  • ConfigSection – wraps xml node/attribute
    namespace PhoneCore.Framework.Configuration
    {
        /// <summary>
        /// Represens a config entry
        /// </summary>
        public class ConfigSection
        {
            private ConfigElement _element;
            public ConfigSection(ConfigElement element)
            {
                _element = element;
            }
    
            /// <summary>
            /// Returns the set of ConfigSections
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public IEnumerable<ConfigSection> GetSections(string xpath)
            {
                return _element.GetElements(xpath).Select(e => new ConfigSection(e));
            }
    
            /// <summary>
            /// Returns ConfigSection
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public ConfigSection GetSection(string xpath)
            {
                return new ConfigSection(new ConfigElement(_element.Node, xpath));
            }
    
            /// <summary>
            /// Returns string
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public string GetString(string xpath)
            {
                return new ConfigElement(_element.Node, xpath).GetString();
            }
    
            /// <summary>
            /// Returns int
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public int GetInt(string xpath)
            {
                ConfigElement element = new ConfigElement(_element.Node, xpath);
                return new ConfigElement(_element.Node, xpath).GetInt();
            }
    
            /// <summary>
            /// Returns type object
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public Type GetType(string xpath)
            {
                return (new ConfigElement(_element.Node, xpath)).GetType();
            }
    
    
            /// <summary>
            /// Returns the instance of T
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public T GetInstance<T>(string xpath)
            {
                return (T)Activator.CreateInstance(GetType(xpath));
            }
    
            /// <summary>
            /// Returns the instance of T
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="xpath"></param>
            /// <param name="args"></param>
            /// <returns></returns>
            public T GetInstance<T>(string xpath, params object[] args)
            {
                return (T)Activator.CreateInstance(GetType(xpath), args);
            }
        }
    } 
    The exposed members are mentioned below:
    • GetSection/GetSections – returns one/multiply ConfigSection GetString – returns value as string using
    • GetInt – returns value as integer
    • GetType – returns value as System.Type
    • GetInstance – returns value as instance of type T
    These members receive the xpath value as parameter. Unfortunately, this isn’t string which represents any XPath expression. The reason of that is WP7 doesn’t support XPath (Jan 2012). Actually, it is a simple XPath expression with limitations.
  • ConfigElement – encapsulates xml processing operations
    namespace PhoneCore.Framework.Configuration
    {
        /// <summary>
        /// Represens a single element of xml
        /// </summary>
        public class ConfigElement
        {
            private string _xpath;
            private XElement _node;
            private XAttribute _attribute;
    
            public ConfigElement(XElement root)
            {
                _node = root;
            }
    
            public ConfigElement(XElement root, string xpath)
            {
                _node = root;
                _xpath = xpath;
                Initialize();
            }
    
            private void Initialize()
            {
                string[] paths = _xpath.Split('/');
    
                XElement current = _node;
                for (int i = 0; i < paths.Length; i++)
                {
                    if (paths[0].StartsWith("@"))
                    {
                        _attribute = current.Attribute(paths[0].Substring(1));
                        return;
                    }
    
                    current = current.Element(paths[i]);
                }
    
                _node = current;
            }
    
            /// <summary>
            /// Returns the set of elements
            /// </summary>
            /// <param name="xpath"></param>
            /// <returns></returns>
            public IEnumerable<ConfigElement> GetElements(string xpath)
            {
                string[] paths = xpath.Split('/');
                int last = paths.Length - 1;
                XElement current = Node;
                for (int i = 0; i < last; i++)
                {
                    current = current.Element(paths[i]);
                }
    
                return current.Elements(paths[last]).Select(x => new ConfigElement(x));
            }
    
            /// <summary>
            /// Returns string
            /// </summary>
            /// <returns></returns>
            public string GetString()
            {
                if (IsAttribute) return _attribute.Value;
                if (IsNode) return _node.Value;
    
                return null;
            }
    
            /// <summary>
            /// Returns int
            /// </summary>
            /// <returns></returns>
            public int GetInt()
            {
                return int.Parse(GetString());
            }
    
            /// <summary>
            /// Returns type
            /// </summary>
            /// <returns></returns>
            public new Type GetType()
            {
                string typeName = GetString();
                return Type.GetType(typeName);
            }
    
            /// <summary>
            /// Returns current XElement
            /// </summary>
            public XElement Node
            {
                get { return _node; }
            }
    
            /// <summary>
            /// Returns current XAttribute
            /// </summary>
            public XAttribute Attribute
            {
                get { return _attribute; }
            }
    
            /// <summary>
            /// true if element represents attribute
            /// </summary>
            public bool IsAttribute
            {
                get { return _attribute != null; }
            }
    
            /// <summary>
            /// true if element represents xml node
            /// </summary>
            public bool IsNode
            {
                get { return _node != null; }
            }
    
        }
    } 
  • Here is the example which shows how to use the configuration:
    ConfigSection bootstrappers = ConfigSettings.Instance.GetSection("system/bootstrapping/bootstrappers/bootstrapper ");
    List<IBootstrapperPlugin> tasks = new List<IBootstrapperPlugin>();
    foreach (var bootsrappersSection in bootstrappers)
    {
        _trace.Info(_category, String.Format("create '{0}' of type '{1}'", bootsrappersSection.GetString("@name"), bootsrappersSection.GetString("@type")));
        var plugin = bootsrappersSection.GetInstance<IBootstrapperPlugin>("@type", bootsrappersSection);
        tasks.Add(plugin);
    }

Diagnostic

Tracing

Default framework’s diagnostic subsystem allows to store all the major application events into a database and do postmortem analysis or review application workflow, performance, etc. At the moment, trace subsystem is implemented and the following are the most important interfaces:
  • ITrace – represents a tracer for tracing subsystem
    namespace PhoneCore.Framework.Diagnostic.Tracing
    {
        /// <summary>
        /// Represents a tracer for tracing subsystem
        /// </summary>
        public interface ITrace: IDisposable
        {
            /// <summary>
            /// Level of tracing
            /// </summary>
            int Level { get; set; }
    
            ...
            /// <summary>
            /// Writes record to trace
            /// </summary>
            /// <param name="record"></param>
            void Info(ITraceRecord record);
    
            ...
            /// <summary>
            /// Writes record to trace
            /// </summary>
            /// <param name="record"></param>
            void Warn(ITraceRecord record);
    
            ...
            /// <summary>
            /// Writes record to trace
            /// </summary>
            /// <param name="record"></param>
            void Error(ITraceRecord record);
    
            ...
            /// <summary>
            /// Writes record to trace
            /// </summary>
            /// <param name="record"></param>
            void Fatal(ITraceRecord record);
    
            /// <summary>
            /// Returns the storage of messages
            /// </summary>
            /// <returns></returns>
            object GetUnderlyingStorage();
    
            /// <summary>
            /// Returns the trace controller
            /// </summary>
            /// <returns></returns>
            ITraceController GetController();
    
            /// <summary>
            /// Shows whether trace is initialized
            /// </summary>
            bool IsInitialized { get; }
    
        }
    }
    As you can see, the interface defines the behavior of logging trace records of different types:
    • Info
    • Warn
    • Error
    • Fatal
    You can interpret these types in your program as you wish
  • ITraceCategory – represents a trace category which helps to group trace records
    namespace PhoneCore.Framework.Diagnostic.Tracing
    {
        /// <summary>
        /// Represents a trace category which helps to group trace records
        /// </summary>
        public interface ITraceCategory
        {
            /// <summary>
            /// Name of trace category
            /// </summary>
            string Name { get; }
        }
    } 
  • ITraceRecord – represents a single trace record
    namespace PhoneCore.Framework.Diagnostic.Tracing
    {
        /// <summary>
        /// Represents a trace record
        /// </summary>
        public interface ITraceRecord
        {
            ITraceCategory Category { get; }
            string Message { get; }
            DateTime Date { get; }
            IPage Page { get; }
            Exception Exception { get;}
            Type SourceType { get; }
        }
    } 

Usage of tracing

You can use the default implementation of tracing which logs trace messages into local database and/or add your custom one. The following section in your application.config file should be defined:
<diagnostic>
     <!-- trace subsystem-->
     <traces>
       <trace name="default" type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTrace, PhoneCore.Framework">
         <storage connectionString="Data Source=isostore:/tracedb.sdf">
           <init>
             <types>
               <type name="Info" />
               <type name="Warn" />
               <type name="Error" />
               <type name="Fatal" />
             </types>
           </init>
         </storage>
         <level>0</level>
         <controller type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTraceController, PhoneCore.Framework"
                     template="Resources/Templates/DefaultTraceReport.txt" />
       </trace>
     </traces>
   </diagnostic>
PhoneCore framework will create the list of traces define here as shown below:
namespace PhoneCore.Framework.Diagnostic.Tracing
{
    /// <summary>
    /// Tracer factory
    /// </summary>
    public class TraceFactory
    {
        public const string Default = "default";
        private static readonly Dictionary<string, ITrace> __traces = new Dictionary<string, ITrace>();
        private static readonly object __lockInstance = new object();

        public static bool IsInitialized { get; private set; }

        static void Initialize()
        {
            try
            {
                //get traces
                var traceConfigs = ConfigSettings.Instance.GetSections("system/diagnostic/traces/trace");
                foreach (var traceConfig in traceConfigs)
                {
                    string name = traceConfig.GetString("@name");
                    ITrace trace = traceConfig.GetInstance<ITrace>("@type", new object[]{traceConfig});
                    __traces.Add(name, trace);
                }
                IsInitialized = true;
            }
            catch(Exception ex)
            {
                //Show user message to make clear the reason
                MessageBox.Show(String.Format("Fatal error: unable to register trace subsystem. Description: {0}", ex));
                throw;
            }
        }

        static void Ensure()
        {
            if (!IsInitialized)
                lock (__lockInstance)
                {
                    if (!IsInitialized)
                        Initialize();
                }
        }

        /// <summary>
        /// Get Default tracer
        /// </summary>
        /// <returns></returns>
        public static ITrace GetTrace()
        {
            Ensure();
            return GetTrace(Default);
        }

        /// <summary>
        /// Gets tracer associated with the given name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static ITrace GetTrace(string name)
        {
            Ensure();
            return __traces[name];
        }
    }
} 
After the definition of the traces in configuration, you can use it using the TraceFactory class which exposes two static methods to access the configured traces:
  • GetTrace() – returns default trace
  • GetTrace(string name) – returns trace by name
Here is the example of trace usage:
private static readonly ITrace _trace = TraceFactory.GetTrace();
private static readonly ITraceCategory _categoryTo = TraceCategory.GetInstance("Navigation.To");
…
_trace.Info(_categoryTo, String.Format("Navigate to {0}", pageUri)); 
This code uses the default trace which writes Info message using "Navigation.To" category which will create automatically at the first call of TraceCategory.GetInstance():
namespace PhoneCore.Framework.Diagnostic.Tracing
{
    /// <summary>
    /// Default implementation of ITraceCategory
    /// </summary>
    public class TraceCategory : ITraceCategory
    {
        private static readonly Dictionary<string, ITraceCategory> __categories =
            new Dictionary<string, ITraceCategory>();

        private static readonly object __lockInstance = new object();

        public string Name { get; set; }

        public TraceCategory(string name)
        {
            Name = name;
        }

        /// <summary>
        /// Returns the instance of trace category
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static ITraceCategory GetInstance(string name)
        {
            if (!__categories.ContainsKey(name))
            {
                lock (__lockInstance)
                {
                    if (!__categories.ContainsKey(name))
                        __categories.Add(name, new TraceCategory(name));
                }
            }
            return __categories[name];
        }

        /// <summary>
        /// Returns registred category names
        /// </summary>
        public static IEnumerable<string> GetRegistredCategoryNames
        {
            get
            {
                lock (__lockInstance)
                    return __categories.Keys;
            }
        }

    }
}  

Here is an example how the trace output looks:

Trace.jpg

Dependency injection container

All major frameworks' components have constructor injection with IContainer instance which represents a Dependency Injection container. It provides the following groups of methods:
  • RegisterType - registers mapping between interface and type which implements this interface or registers concrete implementation only
  • RegisterInstance - registers an existing instance which implements some interface
  • Register - special method which allows to define the interception features programmatically
The instance of container is passed through framework classes and used for registering/resolving of other components, e.g.:
/// <summary>
/// Default implementation of navigation service
/// </summary>
public class NavigationService : INavigationService
{
    …
    protected ConfigSection Config { get; private set; }
    protected IContainer Container { get; private set; }

    public NavigationService(IConfigSection config, IContainer container)
    {
        …
        PageMapping = container.Resolve<IPageMapping>();
    }
} 

This approach has advantages and disadvantages. For example, it allows simplifying creation of unit test (see example below) by avoiding the usage of static instances and /or dependencies at concrete implementations of classes and simplifies the ability to extend/replace frameworks' subsystems. However it hides what the service actually consumes.

Aspect-oriented programming support

Since the version 0.6.1, the framework supports the ability to define custom interceptors which can be attached to methods. This is possible if special proxy class is created for intercepted service at compile time (special tool is already provided for this):

using System.Reflection;
using PhoneCore.Framework.IoC.Interception.Proxies;
namespace PhoneCore.Framework.Storage
{
    public class FileSystemServiceProxy : ProxyBase, PhoneCore.Framework.Storage.IFileSystemService
    {
        public System.IO.Stream OpenFile(System.String path, System.IO.FileMode mode)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path, mode); 
            return RunBehaviors(methodInvocation).GetReturnValue<System.IO.Stream>();
        }

        public void CreateDirectory(System.String path)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path); 
            RunBehaviors(methodInvocation);
        }

        public System.Boolean DirectoryExists(System.String path)
        {
            var methodInvocation = BuildMethodInvocation(MethodBase.GetCurrentMethod(), path); 
            return RunBehaviors(methodInvocation).GetReturnValue<system.boolean>();
        }
    }
}

As you can see, it has stub implementation of interface methods and is inherited from ProxyBase class. The implementation runs the chain of behaviors using the special object of MethodInvocation type. This object contains all necessary information of the executing method of real object. Custom behavior can validate, log, profile, disable and etc. execution of method.

There are two ways how you can attach behaviors to class for which proxy is already generated:

  • Configuration

XML
<interception>
    <behaviors>
      <behavior name="execute" type="PhoneCore.Framework.IoC.Interception.Behaviors.ExecuteBehavior,PhoneCore.Framework" />
    </behaviors>
    <components>
      <component interface="PhoneCore.Framework.UnitTests.Stubs.Container.IClassA,PhoneCore.Framework.UnitTests"
             proxy="PhoneCore.Framework.UnitTests.Stubs.Container.ClassAProxy,PhoneCore.Framework.UnitTests"
             name ="ClassAProxy">
        <behaviors>
          <clear />
          <behavior name="execute" type="PhoneCore.Framework.IoC.Interception.Behaviors.ExecuteBehavior,PhoneCore.Framework" />
          <behavior name="trace" type="PhoneCore.Framework.IoC.Interception.Behaviors.TraceBehavior,PhoneCore.Framework" />
        </behaviors>
      </component>
      <component interface="PhoneCore.Framework.UnitTests.Stubs.Container.IClassB,PhoneCore.Framework.UnitTests"
             proxy="PhoneCore.Framework.UnitTests.Stubs.Container.ClassBProxy,PhoneCore.Framework.UnitTests"
             name ="ClassBProxy">
        <behaviors>
          <clear />
          <behavior name="profile" type="PhoneCore.Framework.IoC.Interception.Behaviors.ProfileBehavior,PhoneCore.Framework" />
        </behaviors>
      </component>
There are the default execute behavior and custom ones for each component.
  • Programmatically:
C#
container.Register(Component.For<IClassC>().ImplementedBy<ClassC>()
                                          .Proxy<ClassCProxy>()
                                          .AddBehavior(new ExecuteBehavior())
                                          .Singleton());

There is the example of batch file which show how you can auto generate proxy classes using special tool provided via NuGet package or project source codes:

@echo off
pushd

cd /D %~dp0

if '%1'=='' ( 
    Set Mode=Debug
) else (
    Set Mode=%1
)
set proxyTargetProject="%~dp0PhoneCore.Framework.UnitTests"
echo %proxyTargetProject%
set doPause=1
if not "%2" == "" set doPause=0
%systemroot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "PhoneCore.Framework.sln" /t:Build /p:Configuration=%Mode%;TargetFrameworkVersion=v4.0
echo run proxy gen utility
PhoneCore.Framework.ProxyGen\bin\%Mode%\PhoneCore.Framework.ProxyGen.exe %proxyTargetProject% %proxyTargetProject%\bin\%Mode% %proxyTargetProject%\Proxies
%systemroot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe  PhoneCore.Framework.UnitTests\PhoneCore.Framework.UnitTests.csproj /t:Test /p:Configuration=%Mode%;TargetFrameworkVersion=v4.0
@if ERRORLEVEL 1 goto fail

:fail
if "%doPause%" == "1" pause
popd
exit /b 1

:end
popd
if "%doPause%" == "1" pause
exit /b 0

The tool receives the following parameters here:

  • Target project root folder where application.config is located
  • Assemblies location
  • Output directory
Important: the tool is looking for name attribute of component node. It interprets the value of the attribute as class name and output file name. If it isn't defined, proxy won't be generated. You can once generate the proxy and remove this attribute to prevent further processing of the component node until the appropriate interface is changed.

Bootstrapping

ApplicationBootstrapper class

Bootstrapping engine runs plugins which should be execute only once at the application startup. The following line need to be added in your App.xaml:
<?xml version="1.0" encoding="utf-8"?>
<Application 
…
xmlns:vm="clr-namespace:SecureBox.UI.ViewModel"
             xmlns:core="clr-namespace:PhoneCore.Framework.Bootstrap;assembly=PhoneCore.Framework" mc:Ignorable="d">
  <!--Application Resources-->
  <Application.Resources>
        <core:ApplicationBootstrapper x:Key="Bootstrapper" d:IsDataSource="False"  />
..
  </Application.Resources></Application>
ApplicationBootstrapper class performs the next actions:
  • create container
  • create bootstrapper service
  • run bootstrapper service

BootsrapperService class

BootsrapperService runs the list of defined plugins:
namespace PhoneCore.Framework.Bootstrap
{
    /// <summary>
    /// Provides default functionality to execute startup plugins
    /// </summary>
    public class BootstrapperService: IBootstrapperService
    {
        //TODO ensure that it runs only once
        private readonly ITrace _trace = TraceFactory.GetTrace();
        private readonly ITraceCategory _category = TraceCategory.GetInstance("Bootstrapping.Service");

        private readonly IBootstrapperPlugin[] _plugins;

        public BootstrapperService(ConfigSection config, IContainer container)
        {
            _trace.Info(_category, "get bootstrappers from configuration");
            var bootstrappers = config.GetSections("bootstrappers/bootstrapper");

            List<IBootstrapperPlugin> tasks = new List<IBootstrapperPlugin>();
            foreach (var bootsrappersSection in bootstrappers)
            {
                _trace.Info(_category, String.Format("create '{0}' of type '{1}'", bootsrappersSection.GetString("@name"), bootsrappersSection.GetString("@type")));
                var plugin = bootsrappersSection.GetInstance<IBootstrapperPlugin>("@type", bootsrappersSection, container);
                tasks.Add(plugin);
            }
            _plugins = tasks.ToArray();
            _trace.Info(_category, String.Format("register plugins: {0}", _plugins.Count()));
        }

        public BootstrapperService(IBootstrapperPlugin[] plugins)
        {
            _trace.Info(_category, String.Format("register plugins: {0}", plugins.Count()));
            _plugins = plugins;
        }

        #region IBootstrapperService members

        /// <summary>
        /// Run all registred bootstrappers
        /// </summary>
        /// <returns></returns>
        public bool Run()
        {
            _trace.Info(_category, "run bootstrapper");
            return _plugins.Aggregate(true, (current, task) => current & task.Run());
        }

        /// <summary>
        /// Updates all registred bootstrappers
        /// </summary>
        /// <returns></returns>
        public bool Update()
        {
            return _plugins.Aggregate(true, (current, task) => current & task.Update());
        }

        /// <summary>
        /// Returns the task registered in bootstrapper by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public IBootstrapperPlugin GetPlugin(string name)
        {
            return _plugins.Single(task => task.Name == name);
        }

        /// <summary>
        /// Returns the task registered in bootstrapper by type
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public IBootstrapperPlugin GetPlugin(Type t)
        {
            return _plugins.Single(task => task.GetType() == t);
        }

        /// <summary>
        ///  Returns the task registered in bootstrapper by type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IBootstrapperPlugin GetPlugin<T>()
        {
            return GetPlugin(typeof(T));
        }

        #endregion
    }
} 
The following methods are defined below:
  • Run() – execute all plugins by calling its Run() method
  • Update() –calls Update() method of each plugin
  • GetPlugin() – gets the registered plugin instance by its type or name
The last group of methods provides the access to the separate instance of registered plugin by type or name:
container.Resolve<IBootstrapperService>().GetPlugin<CoreBootstrapperPlugin>(); 
Let’s look at the major plugin: CoreBootstrapperPlugin

Core plugin

CoreBootstrapperPlugin plugin registers core services in container:
namespace PhoneCore.Framework.Bootstrap
{
    /// <summary>
    /// Registers core services, should be executed first
    /// </summary>
    public class CoreBootstrapperPlugin: BootstrapperPlugin
    {
        public CoreBootstrapperPlugin(ConfigSection config, IContainer container) : base(config, container) { }

        /// <summary>
        /// Registers core services
        /// </summary>
        /// <returns></returns>
        public override bool Run()
        {
            try
            {
                Trace.Info(Category, "register page mapping");
                Container.Register<PageMapping>(ConfigSettings.Instance.GetSection("views/pageMapping"), Container);
                
                Trace.Info(Category, "register navigation service");
                var navigationSection = Config.GetSection("services/navigation");
                Container.Register(typeof (INavigationService), navigationSection.GetType("@type"), navigationSection, Container);
                
                Trace.Info(Category, "register file system service");
                var fileSection = Config.GetSection("services/fileSystem");
                Container.Register(typeof(IFileSystemService), fileSection.GetType("@type"), fileSection, Container);

                Trace.Info(Category, "register settings service");
                var settingSection = Config.GetSection("services/settings");
                Container.Register(typeof(ISettingService), settingSection.GetType("@type"), settingSection, Container);
                return true;
            }
            catch (Exception ex)
            {
                Trace.Fatal(Category, "registration failed", ex);
                throw;
            }
        }

        /// <summary>
        /// Updates
        /// </summary>
        /// <returns></returns>
        public override bool Update()
        {
            //not updatable
            Trace.Info(Category, "update");
            return false;
        }
    }
}
The services should be defined in configuration and implement framework’s interfaces:
<bootstrapper name="Core" type="PhoneCore.Framework.Bootstrap.CoreBootstrapperPlugin, PhoneCore.Framework">
         <services>
           <navigation type="PhoneCore.Framework.Navigation.NavigationService, PhoneCore.Framework" />
           <fileSystem type="PhoneCore.Framework.Storage.IsolatedStorageFileService, PhoneCore.Framework" />
           <settings type="PhoneCore.Framework.Storage.IsolatedStorageSettingService, PhoneCore.Framework" />
         </services>
       </bootstrapper>
Afterwards, user code can consume the registered services. They are registered in the container by the core plugin:
  • PageMapping
  • NavigationService
  • FileSystemService
  • SettingsService
  • IsolatedStorageSettingsService
If there's a need to switch to the usage of your custom implementation, you should create your own implementation of bootstrapper plugin and define it instead of Core.

Custom plugins

You can create your custom plugins which will be executed after Core plugin. All you need is to create class inherited from IBootstrapperPlugin:
namespace PhoneCore.Framework.Bootstrap
{
    /// <summary>
    /// Represents a startup plugin
    /// </summary>
    public interface IBootstrapperPlugin
    {
        /// <summary>
        /// The name of plugin
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Run plugin
        /// </summary>
        /// <returns></returns>
        bool Run();

        /// <summary>
        /// Update plugin
        /// </summary>
        /// <returns></returns>
        bool Update();
    }
}
and add it in configuration section:
<system>
    <!-- bootstrapping -->
    <bootstrapping>
      <bootstrappers>
        <bootstrapper name="Core" type="PhoneCore.Framework.Bootstrap.CoreBootstrapperPlugin, PhoneCore.Framework" />
        <bootstrapper name="Init" type="SecureBox.UI.Infrastructure.Plugins.InitPlugin, SecureBox.UI" />

… 
Note: The order is important. The core plugin must be first as the plugins are executed as they are defined.

Navigation

Interfaces

Navigation engine simplifies the way how your application navigates between pages. It provides the following interfaces:
  • IPage – represents a type of page which is used by mapping for navigation
    namespace PhoneCore.Framework.Navigation
    {
        /// <summary>
        /// Represetns a type of page which is used by navigation subsystem
        /// </summary>
        public interface IPage: IEquatable<IPage>
        {
            string Name { get; }
        }
    }
  • INavigationService – defines the behavior of navigation engine
    namespace PhoneCore.Framework.Navigation
    {
        /// <summary>
        /// Represents navigation service
        /// </summary>
        public interface INavigationService
        {
            /// <summary>
            /// Navigating event
            /// </summary>
            event NavigatingCancelEventHandler Navigating;
            
            /// <summary>
            /// Navigates to uri
            /// </summary>
            /// <param name="pageUri"></param>
            void NavigateTo(Uri pageUri);
    
            /// <summary>
            /// Navigates to uri and passes parameters
            /// </summary>
            /// <param name="pageUri"></param>
            /// <param name="parameters"></param>
            void NavigateTo(Uri pageUri, Dictionary<string, object> parameters);
    
            /// <summary>
            /// Navigates using page instance in mapping
            /// </summary>
            /// <param name="type"></param>
            void NavigateTo(IPage type);
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="type"></param>
            /// <param name="parameters"></param>
            void NavigateTo(IPage type, Dictionary<string, object> parameters);
    
            /// <summary>
            /// Goes back
            /// </summary>
            void GoBack();
        }
    } 
  • IPageResolver – the class which implements this interface should return the corresponding page by its type
    namespace PhoneCore.Framework.Navigation
    {
        public interface IPageResolver
        {
            IPage Resolve(string name);
        }
    } 
Let’s look how the default implementation works.

Implementation

The essential part of navigation is page mapping which is defined through configuration:
<views>
   <!-- page mapping -->
   <pageMapping>
     <resolver type="SecureBox.UI.Infrastructure.PageResolver, SecureBox.UI" />
     <pages>
       <page name="Startup">
         <uri type="relative" address="/ViewPage/StartupViewPage.xaml" />
         <viewModel type="SecureBox.UI.ViewModel.StartupViewPageModel, SecureBox.UI" />
       </page>
       <page name="Library">
         <uri type="relative" address="/ViewPage/LibraryViewPage.xaml" />
         <viewModel type="SecureBox.UI.ViewModel.LibraryViewPageModel, SecureBox.UI" />
       </page>
       <page name="Password">
         <uri type="relative" address="/ViewPage/PasswordViewPage.xaml" />
         <viewModel type="SecureBox.UI.ViewModel.PasswordViewPageModel, SecureBox.UI" />
       </page>
       <page name="Account">
         <uri type="relative" address="/ViewPage/AccountViewPage.xaml" />
         <viewModel type="SecureBox.UI.ViewModel.AccountViewPageModel, SecureBox.UI" />
       </page>
       <page name="Email">
         <uri type="relative" address="/ViewPage/EmailViewPage.xaml" />
         <viewModel type="SecureBox.UI.ViewModel.EmailViewPageModel, SecureBox.UI" />
       </page>
As it`s seen, each page is defined by page node:
  • name – page name which is unique. The resolver should return the page instance by this
  • uri –defines path to this page
  • viewModel – viewModel type of the page. It should implement PhoneCore.Framework.Views.IViewModel interface
Also pageMapping node contains the definition of IPageResolver type which is used for resolving pages. Now let’s focus on how mapping is implemented:
namespace PhoneCore.Framework.Navigation
{
    /// <summary>
    /// Represents page mapping
    /// </summary>
    public class PageMapping
    {
        private readonly object _syncLock = new object();
        private Dictionary<IPage, Tuple<Uri, Lazy<IViewModel>>> _pageMapping = new Dictionary<IPage, Tuple<Uri, Lazy<IViewModel>>>();

        public PageMapping(ConfigSection config, IContainer container)
        {
            var pages = config.GetSections("pages/page");
            var resolverConfig = config.GetSection("resolver");
            IPageResolver resolver = resolverConfig.GetInstance<IPageResolver>("@type", resolverConfig, container);
            foreach (var configPage in pages)
            {
                var name = configPage.GetString("@name");
                var page = resolver.Resolve(name);
                //TODO support other types of uri
                var uri = new Uri(configPage.GetSection("uri").GetString("@address"), UriKind.Relative);
                var vmSection = configPage.GetSection("viewModel");
                lock(_syncLock)
                    _pageMapping.Add(page, new Tuple<Uri, Lazy<IViewModel>>(
                        uri,
                        new Lazy<IViewModel>(() => vmSection.GetInstance<IViewModel>("@type", vmSection, container))));
            }
        }

        public PageMapping(Dictionary<IPage, Tuple<Uri, Lazy<IViewModel>>> mapping)
        {
            _pageMapping = mapping;
        }

        /// <summary>
        /// get page uri by its type
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public Uri GetUri(IPage page)
        {
            return _pageMapping.Single(m => m.Key.Equals(page)).Value.Item1;
        }

        /// <summary>
        /// get page's ViewModel by its type
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public IViewModel GetViewModel(IPage page)
        {
            return _pageMapping.Single(m => m.Key.Equals(page)).Value.Item2.Value;
        }

        /// <summary>
        /// get page type by uri
        /// </summary>
        /// <param name="pageUri"></param>
        /// <returns></returns>
        public IPage GetPage(Uri pageUri)
        {
            return _pageMapping.Keys.Single(m => _pageMapping[m].Item1 == pageUri);
        }

    }
} 
So the constructor creates the dictionary Dictionary <IPage, Tuple<Uri, Lazy<IViewModel>>> where the key is page type and value is tuple containing “lazy” wrapper for IViewModel. It prevents from the creation of each ViewModels during PageMapping initialization.

Miscellaneous

There are the classes which I haven’t described yet. In brief:
  • Primitive types
    • Lazy – provides the implementation of “lazy” type. It is already used by PageMapping class
    • Tuple – tuple class
  • PhoneCore.Framework.Views.Views namespace
    • IViewMode– defines the behavior of ViewModel
    • ViewPage – base class which is used instead of built-in
Let's examine the example of how the application can be implemented with the usage of PhoneCore Framework.

The example of usage: SecureBox application

SecureBox is a Windows Phone 7.5 application which allows to store your sensitive information such as account credentials, phone numbers, passwords and prevents the access to it . Currently the development hasn`t done yet, but its code can help to understand the major advantages of using PhoneCore Framework.

Startup.jpgTemplates.jpg

Let’s concentrate on the key concepts.

Setting up framework

First define application.config:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
  <system>
    <container type="PhoneCore.Framework.Container,PhoneCore.Framework">
      <!--<types>
        <type type="" mapTo="" />
      </types>-->
    </container>
    <!-- bootstrapping -->
    <bootstrapping>
      <bootstrappers>
        <bootstrapper name="Core" type="PhoneCore.Framework.Bootstrap.CoreBootstrapperPlugin, PhoneCore.Framework">
          <services>
            <navigation type="PhoneCore.Framework.Navigation.NavigationService, PhoneCore.Framework" />
            <fileSystem type="PhoneCore.Framework.Storage.IsolatedStorageFileService, PhoneCore.Framework" />
            <settings type="PhoneCore.Framework.Storage.IsolatedStorageSettingService, PhoneCore.Framework" />
          </services>
        </bootstrapper>
        <bootstrapper name="Init" type="SecureBox.UI.Infrastructure.Plugins.InitPlugin, SecureBox.UI" />
        <bootstrapper name="DataContext" type="SecureBox.UI.Infrastructure.Plugins.DataContextPlugin, SecureBox.UI"  />
        <bootstrapper name="PassGen" type="SecureBox.UI.Infrastructure.Plugins.PassGenPlugin, SecureBox.UI"  />
      </bootstrappers>
    </bootstrapping>
    <navigation>
      <service />
    </navigation>
    <diagnostic>
      <!-- trace subsystem-->
      <traces>
        <trace name="default" type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTrace, PhoneCore.Framework">
          <storage connectionString="Data Source=isostore:/tracedb.sdf">
            <init>
              <types>
                <type name="Info" />
                <type name="Warn" />
                <type name="Error" />
                <type name="Fatal" />
              </types>
            </init>
          </storage>
          <level>0</level>
          <controller type="PhoneCore.Framework.Diagnostic.Tracing.DefaultTraceController, PhoneCore.Framework" 
                      template="Resources/Templates/DefaultTraceReport.txt" />
        </trace>
      </traces>
    </diagnostic>
  </system>
  <data>
    <!-- data storage -->
    <storage connectionString="Data Source=isostore:/secureboxdb.sdf">
      <init>
        <!-- predefined templates -->
        <templates>
          <template name="Password" imageUrl="/Resources/Images/Templates/Password.png" />
          <template name="Account" imageUrl="/Resources/Images/Templates/Account.png" />
          <template name="Email" imageUrl="/Resources/Images/Templates/Email.png" />
          <template name="Note" imageUrl="/Resources/Images/Templates/Note.png" />
        </templates>
      </init>
    </storage>
  </data>
  <views>
    <!-- page mapping -->
    <pageMapping>
      <resolver type="SecureBox.UI.Infrastructure.PageResolver, SecureBox.UI" />
      <pages>
        <page name="Startup">
          <uri type="relative" address="/ViewPage/StartupViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.StartupViewPageModel, SecureBox.UI" />
        </page>
        <page name="Library">
          <uri type="relative" address="/ViewPage/LibraryViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.LibraryViewPageModel, SecureBox.UI" />
        </page>
        <page name="Password">
          <uri type="relative" address="/ViewPage/PasswordViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.PasswordViewPageModel, SecureBox.UI" />
        </page>
        <page name="Account">
          <uri type="relative" address="/ViewPage/AccountViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.AccountViewPageModel, SecureBox.UI" />
        </page>
        <page name="Email">
          <uri type="relative" address="/ViewPage/EmailViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.EmailViewPageModel, SecureBox.UI" />
        </page>
        <page name="Note">
          <uri type="relative" address="/ViewPage/NoteViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.NoteViewPageModel, SecureBox.UI" />
        </page>
        <page name="Search">
          <uri type="relative" address="/ViewPage/SearchViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.SearchViewPageModel, SecureBox.UI" />
        </page>
        <page name="Settings">
          <uri type="relative" address="/ViewPage/SettingsViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.SettingsViewPageModel, SecureBox.UI" />
        </page>
        <page name="Health">
          <uri type="relative" address="/ViewPage/HealthViewPage.xaml" />
          <viewModel type="SecureBox.UI.ViewModel.HealthViewPageModel, SecureBox.UI" />
        </page>
      </pages>
    </pageMapping>
  </views>
</settings> 
Then change standard MVVMLight's ViewLocator class to:
  /// <summary>
    /// This class contains static references to all the view models in the
    /// application and provides an entry point for the bindings.
    /// </summary>
    public class ViewModelLocator
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
        }
        
        private PageMapping _pageMapping;
        protected PageMapping PageMapping
        {
            get
            {
                if(_pageMapping == null)
                {
                    var bootstrapper = Application.Current.Resources["Bootstrapper"] as ApplicationBootstrapper;
                    IContainer container = bootstrapper.GetContainer();
                    _pageMapping = container.Resolve<PageMapping>();
                }

                return _pageMapping;
            }
            
    
        }

        #region ViewModel properties
        /// <summary>
        /// Gets the Startup property which defines the main viewmodel.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public IViewModel Startup
        {
            get
            {
                return PageMapping.GetViewModel(Page.Get(Page.EnumType.Startup));//_startup;
            }
        }
…
}
Afterwards define ApplicationBootstrapper as application resource in App.xaml:
<?xml version="1.0" encoding="utf-8"?>
<Application x:Class="SecureBox.UI.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:SecureBox.UI.ViewModel"
             xmlns:core="clr-namespace:PhoneCore.Framework.Bootstrap;assembly=PhoneCore.Framework" mc:Ignorable="d">
  <!--Application Resources-->
  <Application.Resources>
        <core:ApplicationBootstrapper x:Key="Bootstrapper" d:IsDataSource="False"  />
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
  </Application.Resources>
  <Application.ApplicationLifetimeObjects>
    <!--Required object that handles lifetime events for the application-->
    <shell:PhoneApplicationService Launching="Application_Launching" Closing="Application_Closing" Activated="Application_Activated" Deactivated="Application_Deactivated" />
  </Application.ApplicationLifetimeObjects>
</Application>
After these changes the application bootstrapper will be executed automatically. Let’s focus on SecureBox design and code.

Business layer

Domain entities are represented by simple classes which contain only public properties. These classes are known as Plain Old CLR Objects (POCOs): Image 5

Domain.png

There are:

  • Record – stores user defined data in single entity
  • Template – represents a generic template for user record. There are the following built-in ones:
    • Password
    • Account
    • Email
    • Note
    Each template consists of built-in fields which are stored in Properties collection
  • Keyword – represents a category of Record. It allows to bind the record to different categories. This helps to search it in the collection
  • Property – represents a single field of record which stores the unit of user data
There are the following actions on an entity permitted:
  • Creation
  • Editing
  • Removing (not implemented yet)

Data Layer

Database schema

There is the following schema of a database which is used for storing our business entities:

Datalayer.png

Image 8

The different enterprise patterns can be used to access data stored in database:

  • Repository
  • Unit of Work
  • Transaction script
The entities which are mapped to the tables directly, can be generated by using the auto generation tool:
sqlmetal c:\temp\ secureboxdb.sdf /code:"c:\temp\ secureboxdb.cs" /language:csharp /namespace: SecureBox.UI.Infrastructure.Data /context: SecureBoxDataContext /pluralize
Here is the example of result:
[Table(Name="Keywords")]
public partial class Keyword : INotifyPropertyChanging, INotifyPropertyChanged
{
…
[Table(Name="Properties")]
public partial class Property : INotifyPropertyChanging, INotifyPropertyChanged
{
…
[Table(Name="Records")]
public partial class Record : INotifyPropertyChanging, INotifyPropertyChanged
{
…
[Table(Name="RecordKeywords")]
public partial class RecordKeyword : INotifyPropertyChanging, INotifyPropertyChanged
{
    …
[Table(Name="Templates")]
public partial class Template : INotifyPropertyChanging, INotifyPropertyChanged
{
Also the command will generate the class derived from System.Data.Linq.DataContext, which is example of “Unit of Work” pattern:
[System.Data.Linq.Mapping.DatabaseAttribute(Name="secureboxdb")]
public partial class SecureBoxDataContext : System.Data.Linq.DataContext
{
    …
For domain logic, I decided to use Repository pattern and encapsulate the work with data into single class which is stored in container as IDataContextService:
namespace SecureBox.UI.Infrastructure.Data
{
    /// <summary>
    /// A gateway to data layer
    /// </summary>
    public interface IDataContextService: IDisposable
    {
        IRepository<Model.Template> Templates { get; }
        IRepository<Model.Record> Records { get; }
        IRepository<Model.Keyword> Keywords { get; }
        IRepository<Model.Property> Properties { get; }
        event EventHandler<EventArgs> OnChanged;
        void Delete();
        void Commit();
    }
} 
As you can see, it exposes entities related to our domain layer, not to data layer

Initialization

The right way to initialize and register the code which is working with data is custom bootstrapper plugin:
namespace SecureBox.UI.Infrastructure.Plugins
{
    /// <summary>
    /// Initializes DataContextService
    /// </summary>
    public class DataContextPlugin : BootstrapperPlugin
    {
        public DataContextPlugin(ConfigSection config, IContainer container) : base(config, container) { }

        public override bool Run()
        {
            try
            {
                Trace.Info(Category, "register data context instance");

                //get connection string
                string connectionString =
                    ConfigSettings.Instance.GetSection("data/storage").GetString("@connectionString");

                //create DataContext instance
                SecureBoxDataContext dataContext = new SecureBoxDataContext(connectionString);
                //register data context service
                Container.Register<IDataContextService, DataContextService>(dataContext);
                //check whether database exists
                if(dataContext.DatabaseExists())
                    dataContext.DeleteDatabase();

                if (!dataContext.DatabaseExists())
                {
                   //  dataContext.DeleteDatabase();
                   // }
                    CreateDatabase(dataContext);
                }

                return true;
            }
            catch (Exception ex)
            {
                Trace.Fatal(Category, "run is failed", ex);
                throw;
            }
        }

Here is the code, which runs always one time:

/// <summary>
/// Creates dataBase
/// </summary>
/// <param name="dataContext"></param>
private void CreateDatabase(SecureBoxDataContext dataContext)
{
    Trace.Info(Category, "create database");
    dataContext.CreateDatabase();

    IDataContextService dataContextService = Container.Resolve<IDataContextService>();

    Trace.Info(Category, "insert templates from configuration");
    var templates = ConfigSettings.Instance.GetSections("data/storage/init/templates/template");
    foreach (var templateConfig in templates)
    {
        var name = templateConfig.GetString("@name");
        var imageUrl = templateConfig.GetString("@imageUrl");
        dataContextService.Templates.Add(new Template() {Name = name, ImageUrl = imageUrl});
    }
    dataContextService.Commit();
} 

Presentation layer

View

User interface is constructed by separation of markup and code using MVVM pattern. I use MVVMLight framework for this:
namespace SecureBox.UI.ViewModel
{
    /// <summary>
    /// Provides ViewModel's base functionaluty
    /// </summary>
    public class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase, IViewModel
    {
        protected INavigationService NavigationService { get; set; }
        protected ISettingService SettingService { get; set; }
        protected  ConfigSection Config { get; private set; }
        protected IContainer Container { get; private set; }

        public ViewModelBase(ConfigSection config, IContainer container): base()
        {
            Config = config;
            Container = container;
            NavigationService = Container.Resolve<INavigationService>();
            SettingService = Container.Resolve<ISettingService>();
        }

        private Dictionary<string, object> _navigationParameters = null;
        public Dictionary<string, object> NavigationParameters
        {
            get
            {
                return _navigationParameters;
            }
            set
            {
                _navigationParameters = value;
                ReadNavigationParameters();
            }
        }

        protected virtual void ReadNavigationParameters()
        {
        }

        /// <summary>
        /// Saves view model state into dictionary
        /// </summary>
        /// <param name="state"></param>
        public virtual void SaveStateTo(IDictionary<string, object> state)
        {

        }

        /// <summary>
        /// Restores view model state from dictionary
        /// </summary>
        /// <param name="state"></param>
        public virtual void LoadStateFrom(IDictionary<string, object> state)
        {

        }
    }
} 
Here is the default view model class which implements PhoneCore interface and derived from MVVMLight’s base class. This approach allows us to use such useful method as RaisePropertyChanged. Let’s look at single template: Account. Here is the markup:
<Views:ViewPage
    x:Class="SecureBox.UI.ViewPage.AccountViewPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" xmlns:custom="clr-namespace:SecureBox.Controls;assembly=SecureBox.Controls" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71" xmlns:Views="clr-namespace:PhoneCore.Framework.Views;assembly=PhoneCore.Framework" FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480"
    DataContext="{Binding Account, Source={StaticResource Locator}}"
    shell:SystemTray.IsVisible="False">
    <Grid x:Name="LayoutRoot">
        <Grid.Background>
            <ImageBrush ImageSource="/Resources/Images/LibraryBackground.jpg"/>
        </Grid.Background>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="SECURE BOX" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="account" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ScrollViewer>
                <StackPanel Orientation="Vertical">
                    <!-- name -->
                    <TextBlock Text="Name"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,0,0,0"/>
                    <toolkit:PhoneTextBox Hint="name of your password in library"
                                    Text="{Binding Name, Mode=TwoWay}"
                                    MaxLength="64"
                                    LengthIndicatorVisible="True"
                                    InputScope="Text"/>
                    <!-- keywords -->
                    <TextBlock Text="Keywords"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,-24,0,0"/>
                    <toolkit:PhoneTextBox Hint="keywords for searching"
                                    Text="{Binding Keyword, Mode=TwoWay}"  
                                    MaxLength="64"
                                    LengthIndicatorVisible="True"
                                    InputScope="Text"/>

                    <!-- account name -->
                    <TextBlock Text="Account name"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,-24,0,0"/>
                    <toolkit:PhoneTextBox Hint="your account name/login"
                                    Text="{Binding AccountName, Mode=TwoWay}"  
                                    MaxLength="64"
                                    LengthIndicatorVisible="True"
                                    InputScope="Text"/>

                    <!-- password -->
                    <TextBlock Text="Password"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,-24,0,0"/>
                    <toolkit:PhoneTextBox Hint="your password"
                                    Text="{Binding Password, Mode=TwoWay}"  
                                    ActionIcon="/Resources/Images/Actions/Refresh.png"
                                    MaxLength="200"
                                    LengthIndicatorVisible="True"
                                    InputScope="Text">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="ActionIconTapped">
                                <cmd:EventToCommand Command="{Binding PasswordActionIconTappedCommand}" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </toolkit:PhoneTextBox>

                    <!-- email name -->
                    <TextBlock Text="Email"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,-24,0,0"/>
                    <toolkit:PhoneTextBox Hint="email related to account"
                                    Text="{Binding Email, Mode=TwoWay}"  
                                    MaxLength="64"
                                    LengthIndicatorVisible="True"
                                    InputScope="EmailNameOrAddress"/>

                    <!-- description name -->
                    <TextBlock Text="Description"
                           Foreground="{StaticResource PhoneSubtleBrush}"
                           Margin="12,-24,0,0"/>
                    <toolkit:PhoneTextBox Hint="some description"
                                    Text="{Binding Description, Mode=TwoWay}"  
                                    MaxLength="500"
                                    MinHeight="240"
                                    TextWrapping="Wrap"
                                    AcceptsReturn="True" 
                                    LengthIndicatorVisible="True"
                                    InputScope="Text"/>
                </StackPanel>
            </ScrollViewer>
        </Grid>
        <custom:BindableApplicationBar x:Name="AppBar" BarOpacity="0.2">
            <custom:BindableApplicationBarIconButton Command="{Binding SaveCommand}" IconUri="/Resources/Images/AppBar/appbar.save.png" Text="Save" />
            <custom:BindableApplicationBarIconButton Command="{Binding CancelCommand}" IconUri="/Resources/Images/AppBar/appbar.cancel.png" Text="Cancel" />
        </custom:BindableApplicationBar>

    </Grid>
 </Views:ViewPage>
As you may notice, here I’m using Silverlight toolkit. You should take a look at:
  • root tag - I switched to usage of PhoneCore framework page
  • data context is set to usage of our ViewModel locator class

ViewModel

Below is the view model of the page:
namespace SecureBox.UI.ViewModel
{
    /// <summary>
    /// Account template view model
    /// </summary>
    public class AccountViewPageModel: TemplateViewPage
    {
        public AccountViewPageModel(ConfigSection config, IContainer container)
            : base(config, container)
        {
            PasswordActionIconTappedCommand = new RelayCommand(OnGeneratePassword);
        }

        #region Methods

        /// <summary>
        /// Sets new password to Password input
        /// </summary>
        private void OnGeneratePassword()
        {
            //TODO extend implementation
            Password = Container.Resolve<IPasswordGenerator>().Generate();
            RaisePropertyChanged("Password");
        }


        #endregion

        /// <summary>
        /// Returns the properites of template
        /// </summary>
        /// <returns></returns>
        protected override IEnumerable<Property> GetProperties()
        {
            return new []
            {
                 new Property() {Name = "AccountName", Value = AccountName},   
                 new Property() {Name = "Password", Value = Password},
                 new Property() {Name = "Email", Value = Email},
                 new Property() {Name = "Description", Value = Description},
            };
        }

        protected override string GetTemplateName()
        {
            return "Account";
        }

        protected override bool IsValid()
        {
            return base.IsValid();
        }


        protected override void FillProperties()
        {
            Name = EditRecord.Name;
            Keyword = GetKeywordsField();
            AccountName = GetPropertyValueByName("AccountName");
            Password = GetPropertyValueByName("Password");
            Email = GetPropertyValueByName("Email");
            Description = GetPropertyValueByName("Description");
        }

        protected override void EraseProperties()
        {
            Name = string.Empty;
            Keyword = string.Empty;
            AccountName = string.Empty;
            Password = string.Empty;
            Email = string.Empty;
            Description = string.Empty;
        }


        #region  Binding properties

        public string AccountName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
        public string Description { get; set; }

        #endregion

        #region Command properties

        public RelayCommand PasswordActionIconTappedCommand
        {
            get;
            private set;
        }

        #endregion
       
    }
} 
On my opinion the implementation is trivial: it is derived of base class of all templates which inherits ViewModelBase I mentioned before:
public abstract class TemplateViewPage: ViewModelBase 
    {
        protected readonly IDataContextService dataContextService;
…
        public TemplateViewPage(ConfigSection config, IContainer container)
            : base(config, container) 
        {
            dataContextService = Container.Resolve<IDataContextService>();

            SaveCommand = new RelayCommand(OnSaveCommand);
            CancelCommand = new RelayCommand(OnCancelCommand);
        }
Let’s look closer at unit test for the view model.

Unit testing

PhoneCore Framework tries to avoid the usage of static classes or method as they interfere to create well tested code. Instead, the dependency injection container is passed through all components of the framework. Dependency injection and MVVM patterns help to create testable code for your ViewModel classes . Here is the test which checks the fact whether we can create the record. It emulates the workflow of UI controls of account template:
namespace SecureBox.UI.UnitTests.ViewModel
{
    [TestClass]
    public class AccountViewPageModelTests
    {
        [TestMethod]
        public void CanSave()
        {
            //arrange
            var configContainer = ConfigSettings.Instance.GetSection("system/container");
            Container container = new Container(configContainer);
            IBootstrapperService service = new BootstrapperService(
                ConfigSettings.Instance.GetSection("system/bootstrapping"),
                container);
            service.Run();

            AccountViewPageModel viewModel = new AccountViewPageModel(null, container);
            viewModel.AccountName = "TestAccount";
            viewModel.Email = "email@test.com";
            viewModel.Keyword = "test";
            viewModel.Password = "my password";
            viewModel.Name = "test name";

            //act
            viewModel.OnSaveCommand();

            using (IDataContextService dataContextService = container.Resolve<IDataContextService>())
            {
                //Assert
                var record = dataContextService.Records.Get(r => r.Name == "test name");
                Assert.AreEqual("Account", record.Template.Name);
                Assert.AreEqual("test name", record.Name);
                Assert.AreEqual("email@test.com", record.Properties.Single(p => p.Name == "Email").Value);
                Assert.AreEqual("my password", record.Properties.Single(p => p.Name == "Password").Value);
                Assert.AreEqual("test", record.Keywords.Single(p => p.Name == "test").Name);
                dataContextService.Delete();
            }
        }
    }
} 
Image 9

Conclusion

At the moment, the following applications are using the PhoneCore Framework:
  • SecureBox - a Windows Phone 7 application which allows to store sensitive information in secure storage, it is described here
  • Phone Guitar Tab - guitar tab viewer for Windows Phone 7. Provides search&download tab/images engine (uses ultimate-guitar.com and last.fm)
I will update wiki documentation at official project site according to the development progress.

Points of interests

  • Performance measurement
  • Configuration improvement
  • Additional feature implementation

Updates

The framework is available as NuGet package here.

Changes:

Version 0.6.1 released 02/11/2012

  • AOP support
    • proxy generation at compile time by special tool using configuration information for types which implement some interface
    • custom interceptors for methods of these types
  • Container changes and improvements
    • RegisterInstance method that registers existing object instance
    • Object lifetime managers
    • RegisterXXX/Resolve by name
    • Fluent interface for RegisterXXX methods
  • Refactoring
    • Tracing: now trace is used as service registered in container
    • Page mapping: interface is extracted

Version 0.5.1 released 01/20/2012:

    • fixed configuration path errors
    • extracted interface from ConfigSection
    • container supports default type mapping registration which is defined in configuration

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Nokia
Germany Germany
Interested in design/development of framework functionality using the best patterns and practices.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Dean Oliver31-Jan-12 18:54
Dean Oliver31-Jan-12 18:54 
QuestionLooks pretty complete Pin
Sacha Barber20-Jan-12 5:06
Sacha Barber20-Jan-12 5:06 
AnswerRe: Looks pretty complete Pin
Ilya Builuk20-Jan-12 5:44
Ilya Builuk20-Jan-12 5:44 
GeneralRe: Looks pretty complete Pin
Sacha Barber20-Jan-12 5:54
Sacha Barber20-Jan-12 5:54 
GeneralRe: Looks pretty complete Pin
Ilya Builuk20-Jan-12 6:45
Ilya Builuk20-Jan-12 6:45 
GeneralRe: Looks pretty complete Pin
Sacha Barber20-Jan-12 7:55
Sacha Barber20-Jan-12 7:55 
GeneralRe: Looks pretty complete Pin
Sacha Barber20-Jan-12 8:10
Sacha Barber20-Jan-12 8:10 
GeneralRe: Looks pretty complete Pin
Ilya Builuk20-Jan-12 9:09
Ilya Builuk20-Jan-12 9:09 
GeneralRe: Looks pretty complete Pin
Sacha Barber20-Jan-12 22:25
Sacha Barber20-Jan-12 22:25 
GeneralRe: Looks pretty complete Pin
Ilya Builuk20-Jan-12 23:48
Ilya Builuk20-Jan-12 23:48 
GeneralRe: Looks pretty complete Pin
Sacha Barber21-Jan-12 3:19
Sacha Barber21-Jan-12 3:19 
GeneralRe: Looks pretty complete Pin
Ilya Builuk21-Jan-12 10:07
Ilya Builuk21-Jan-12 10:07 
GeneralRe: Looks pretty complete Pin
Sacha Barber21-Jan-12 3:21
Sacha Barber21-Jan-12 3:21 
QuestionNice: 5 from me. Pin
Ratish Philip20-Jan-12 2:20
Ratish Philip20-Jan-12 2:20 
GeneralMy vote of 5 Pin
maq_rohit19-Jan-12 1:46
professionalmaq_rohit19-Jan-12 1:46 
GeneralMy vote of 5 Pin
Kanasz Robert19-Jan-12 0:45
professionalKanasz Robert19-Jan-12 0:45 
GeneralMy vote of 5 Pin
Colin Eberhardt18-Jan-12 23:41
Colin Eberhardt18-Jan-12 23:41 
GeneralMy vote of 5 Pin
bigmulu18-Jan-12 23:30
bigmulu18-Jan-12 23:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.