Click here to Skip to main content
Click here to Skip to main content

A framework for building of WP7 application

By , 11 Feb 2012
 

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

<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:
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):

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

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();
            }
        }
    }
} 

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)

About the Author

Ilya Builuk
Software Developer (Senior) Invention Machine
Belarus Belarus
Interested in design/development of framework functionality using the best patterns and practices.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberDean Oliver31-Jan-12 18:54 
Great article loads of effort put in well thought out. If i can give you little tip when creating a singleton class rather do it like this, but great work.
private readonly static ConfigSettings _instance = new ConfigSettings();
public static ConfigSettings Instance
{
get
{
return _instance;
}
}
QuestionLooks pretty completemvpSacha Barber20-Jan-12 5:06 
Looks like you put a lot into it, hope it does well. Have not looked into complete code, but why would you not use existing IOC Containers, or do you?
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

AnswerRe: Looks pretty completememberIlya Builuk20-Jan-12 5:44 
Thanks for your feedback! When I started development, I had looked at Unity, but if I'm not mistaken, it has dependency at other Pattern&Practice libraries that isn't likely. My implementation is based on existed one which I found in Internet (I don't remember exactly which one. May be from here: http://stackoverflow.com/questions/2756151/dependency-injection-for-windows-phone-7). In addition, I added some useful members and extracted generic interface. There is no dependency on my implementation: it is dynamically created from type defined in configuration:
 
 <container type="PhoneCore.Framework.Container,PhoneCore.Framework">
      <!-- right now you can register mapping only for types which have default constructor-->
      <types>
        <type type="PhoneCore.Framework.UnitTests.Stubs.Container.IClassA, PhoneCore.Framework.UnitTests" mapTo="PhoneCore.Framework.UnitTests.Stubs.Container.ClassA, PhoneCore.Framework.UnitTests" />
      </types>
    </container>
 
 
Note: sure, this approach has some performance impact.
 
However, it allows you create a wrapper around existing container which implements the interface.
 
P.S. Which IoC container could you suggest?
GeneralRe: Looks pretty completemvpSacha Barber20-Jan-12 5:54 
I like MEF, but that is not on Phone. Also like Castle (again not on Phone).
Autofac/StructureMap are also very good again not for phone, and Func is an interesting one.
 
In short there are loads none for phone (but I don't know phone stuff, so some may be ok for phone).
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Looks pretty completememberIlya Builuk20-Jan-12 6:45 
Thanks!
I guess my existing implementation isn't bad, but I should find the way to improve it (performance, fault-tolerance, etc) and do not forget about alternative.
GeneralRe: Looks pretty completemvpSacha Barber20-Jan-12 7:55 
IOC Containers are pretty complex beasts if you do it all the way. For example Castles dynamic proxy creates a new type using Reflection.Emit, as such it supports inception (as does Unit). Mef can be bent to do this to, use ExportProviders.
 
But for what you need your implementation is fine right. Simple does the job. Its more of a fancy dictionary (more like a service locator) than an full blown IOC container, the way I see it. No offence intended.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Looks pretty completemvpSacha Barber20-Jan-12 8:10 
One thing I would say is that there is just too much config for me.
 
For example in my own cinch MVVM framework for SL/WPF for me to get a dependency injected I just do this
 
[ExportViewModel("ImageLoaderViewModel")]
public class ImageLoaderViewModel : ViewModelBase
{
    private IViewAwareStatus viewAwareStatusService;
    private IMessageBoxService messageBoxService;
    private IOpenFileService openFileService;
    private ISaveFileService saveFileService;
    private IUIVisualizerService uiVisualizerService;
 
    [ImportingConstructor]
    public ImageLoaderViewModel(
        IMessageBoxService messageBoxService,
        IOpenFileService openFileService,
        ISaveFileService saveFileService,
        IUIVisualizerService uiVisualizerService,
        IViewAwareStatus viewAwareStatusService)
    {
        //setup services
        this.messageBoxService = messageBoxService;<
        this.openFileService = openFileService;
        this.saveFileService = saveFileService;
        this.uiVisualizerService = uiVisualizerService;
        this.viewAwareStatusService = viewAwareStatusService;
    }
}
 
 

No config at all, my framework does everything for you.
 
I think service resolution only gets you part way to happy place, IOC is better. I you are using Laurent (MVVMLight) ViewModelLocator, just delegate that to a full blown IOC container Resolve(typeof(someViewModel)) and it would get all dependencies.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Looks pretty completememberIlya Builuk20-Jan-12 9:09 
Thanks! I will look at this. Regarding configs, I'm facing at work with a lot of configs which configure the whole system at start (however, it isn't mobile application, but enterprise), configs in Continuous Intergration (Cruise Control.NET), etc. So, it isn't problem for me and I think this is the reasonable price paid for flexibility.
GeneralRe: Looks pretty completemvpSacha Barber20-Jan-12 22:25 
ccnet.config, and NANT, sounds great until you realise what a monster you have created. We have about 100 projects, and about 20 services all done through cruise. Truth is I hate it. We are looking to swap Cruise Control for Jenkins, much more modern no NANT all UI and PowerShell
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Looks pretty completememberIlya Builuk20-Jan-12 23:48 
Last several weeks I'm setting up full deployment process of large enterprise aplication using CruiseControl.NET and customs add-ons.. Briefly, it should do for several boxes under load balancer:
* checkout sources
* build package
* do smoke test of already deployed version of application
* deploy
* check deploy
* do smoke test of deployed version
* fail if there are difference between versions
* additional jobs
* prepare reporting
* ...
 
All this stuff use custom configs and so on. Finally, it should provide a lot of information for diagnostics/statistics. I guess it isn't work for developer, but Devops. However, I think it is good experience for every professional software engineer.
 
P.S. I've updated code to store configuration in different files: http://phonecore.codeplex.com/wikipage?title=Configuration[^]
GeneralRe: Looks pretty completemvpSacha Barber21-Jan-12 3:19 
You should look at Jenkins and TeamCity they are so much better than CC. But yeah it's good to get understanding of it. Though our ccnet.config its massive now, we build WCF service reference and run many command lines, such as the god aweful Mage.exe. Horrible thing for click once deployment
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Looks pretty completememberIlya Builuk21-Jan-12 10:07 
Thanks! I will look at this
GeneralRe: Looks pretty completemvpSacha Barber21-Jan-12 3:21 
Oh by the way what a lot of the other IOC vendors do instead of config (which the support too of course) is have installers. So in your real project (say WPF/Phone/SL one), in your App.xaml.cs or phone equivalent, you install real app stuff using real app installer. Then in test you have test installer to install all your test doubles. Keep config very clean.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

QuestionNice: 5 from me.memberRatish Philip20-Jan-12 2:20 
Keep it up! 5 from me!
cheers,
Ratish Philip
Blog: http://wpfspark.wordpress.com/

GeneralMy vote of 5membermaq_rohit19-Jan-12 1:46 
Excellent !!!
GeneralMy vote of 5mvpKanasz Robert19-Jan-12 0:45 
Hi. Good idea. Well done Smile | :) you've got my 5.
GeneralMy vote of 5mvpColin Eberhardt18-Jan-12 23:41 
Looks like you have put a lot of effort into this. Good luck with it.
GeneralMy vote of 5memberbigmulu18-Jan-12 23:30 
tyt!

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130617.1 | Last Updated 12 Feb 2012
Article Copyright 2012 by Ilya Builuk
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid