How to use .NET RIA Services in PRISM

For Webcast overview of process CLICK HEREImportant note: the Presenter being shown on the screen during the Webcast is actually being utilized by the Silverlight, RIA, WPF and Winforms applications (they all utilize the PRISM framework using the Model-View-Presenter and Model-View-ViewModel patterns - see architectural note at end of blog). 

Tim Heuer's presentation on RIA services for Silverlight 3
http://silverlight.net/learn/learnvideo.aspx?video=245417

Tim Heuer does an excellent presentation of RIA services on the above link.   The problem for PRISM developers is that the RIA data access layer resides in the presentation layer’s bootstrap application RIABusApp (ref image below).   Since our RIABusApp will reference our module(s) this poses a problem because now our modules cannot reference our RIA data layer (which resides in RIABusApp) because of a circular reference.   The key to making real RIA work with PRISM is to be able to have the modules reference the bootstrap application RIABusApp.

Fortunately Silverlight allows us to load assemblies dynamically.  With a few lines of code we can load the module and call its initialize mapping without ever having made a reference to the Module.  This is practical with PRISM because most of the business logic will reside in a module minimizing the requirement for any references in RIABusApp.

Code follows:

using System;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Unity;
using PrismContrib.Base;
using PrismContrib.Events;
using PrismContrib.Interfaces;

namespace RIABusApp
{
    public partial class App : Application
    {
        private IBootstrapperProcess bootstrapperProcess = null;

        [Dependency]
        public IUnityContainer Container { get; set; }
        private IUnityContainer container; 

        public App()
        {
            this.Startup += this.Application_Startup;
            this.UnhandledException += this.Application_UnhandledException;

            InitializeComponent();
        }

        void Application_Startup(object sender, StartupEventArgs e)
        {
            this.Resources.Add("RiaContext", RiaContext.Current);
            Bootstrapper<MainPage> bootstrap = new Bootstrapper<MainPage>();
            bootstrap.OnConfigureContainer += OnConfigureContainer;
            bootstrap.OnInitializeModules += OnInitializeModules;
            bootstrap.InitParams = e.InitParams;
            bootstrap.Run();
        }
        /// <summary>
        /// Dynamically load the Mapping Module because it will have
        /// a reference set to this assembly to gain access to the
        /// Services\DataLayerDefault class
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="PrismContrib.Events.BootStrapperEventArgs"/>
        /// instance containing the event data.</param>
        void OnInitializeModules(object sender, BootStrapperEventArgs e)
        {
            container = e.Container;
            WebClient client = new WebClient();

            client.OpenReadCompleted +=
                new OpenReadCompletedEventHandler(OnAssemblyOpened);

            // Requires assembly to be in the RIABusApp.Web ClientBin folder
            client.OpenReadAsync(
                new Uri("ISO15926.Module.Mapping.Silverlight.dll", UriKind.Relative));
        }
        void OnAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
        {
            AssemblyPart assemblyPart = new AssemblyPart();
            Assembly assembly = assemblyPart.Load(e.Result);

            // Find our mapping module in the list returned by GetTypes
            Type mappingType = assembly.GetTypes()
                .First(m => m.FullName.Contains("MappingModule"));

            // Dynamically instantiate our module
            IModule module = (IModule) Activator.CreateInstance(mappingType);

            if (module != null)
            {
                // Buildup doesn't seem to be working on dynamically
                // created class...  Will set container manually
                // => container.BuildUp(module);
                ((IContainer)module).Container = container;
                module.Initialize();
            }
        }

        void OnConfigureContainer(object sender, PrismContrib.Events.BootStrapperEventArgs e)
        {
            e.Container.RegisterType<IControlProcessor, ControlProcessorWPF>();
        }

        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            // If the app is running outside of the debugger then report the exception using
            // a ChildWindow control.
            if (!System.Diagnostics.Debugger.IsAttached)
            {
                // NOTE: This will allow the application to continue running after an exception has been thrown
                // but not handled.
                // For production applications this error handling should be replaced with something that will
                // report the error to the website and stop the application.
                e.Handled = true;
                ChildWindow errorWin = new ErrorWindow(e.ExceptionObject);
                errorWin.Show();
            }
        }
    }
}

The application that we're using for this blog is a multi-targeting application; this means that we can have multiple platforms using the same code (as shown in the projects underlined above).  In this case RIA Services will be utilizing the Silverlight module.  Below you'll see all of the client side code that is required to return a class list, as Tim noted in his presentation all of the projection is handled for us.

The code on server side is no more complicated as shown below.

In PRISM, because of dependency injection, all we have to do to consume our service is request a reference to IDataService (constructor injection), request the classes and assign the results to the model.Classes property.

Note: The SemWeb infrastructure (which utilizes my own PRISMContrib project) handles the event notification for us.   SemWeb is scheduled to be released late August at http://www.CodePlex.com/SemWeb.

The source code for the MappingPresenter follows: 

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using ISO15926.Library;
using ISO15926.Module.Mapping.Services;
using Microsoft.Practices.Unity;
using Microsoft.Windows.Controls;
using PrismContrib.Base;
using PrismContrib.Events;

namespace ISO15926.Module.Mapping.Views.InformationModel
{
    /// <summary>
    /// Information Model presenter
    /// </summary>
    public class InformationModelPresenter : PresenterBase<IInformationModelView>
    {
        private ISemWebPresentationModel model;

        private DataGrid grdClasses {
            get { return GetControl<DataGrid>("dgClasses"); }
        }

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="InformationModelPresenter"/> class.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="model">The model.</param>
        /// <param name="container">The container.</param>
        /// <param name="riaDAL">The ria DAL.</param>
        public InformationModelPresenter(
            IInformationModelView view,
            ISemWebPresentationModel model,
            IUnityContainer container,
            IDataService riaDAL) : base(view, model, container)
        {
            this.model = model;

            // Populate model with ISO15926 Class
            model.Classes = riaDAL.GetClassList();
        }

        private void OnDataLoaded()
        {
            grdClasses.ItemsSource = model.Classes;
        }

        #region OVERRIDES of baseclass methods  

        #region METHOD: OnViewSizeSet
        /// <summary>
        /// Called when [view size set].  Sets the data grid dimensions
        /// to the current view
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        protected override void OnViewSizeSet(object sender, SizeChangedEventArgs e)
        {
            // Filter for this view
            IInformationModelView view = sender as IInformationModelView;
            if (view == null)
                return;

            // Set grid height and width to view dimensions so that
            // we'll have scrollbars
            grdClasses.Height = e.NewSize.Height;
            grdClasses.Width = e.NewSize.Width;

            base.OnViewSizeSet(sender, e);
        }
       
        #endregion
        #region METHOD: OnButtonClickEventHandler
        /// <summary>
        /// Handles all Button/CheckBox Click events
        /// <see cref="E:ClickEventHandler"/>
        /// </summary>
        /// <param name="e">The <see cref="ClickEventArgs"/>
        /// instance containing the event data.</param>
        public override void OnButtonClickEventHandler(ClickEventArgs e)
        {
            e.IsHandled = e.Name.Contains("DetailsView_ClickMe");

            if (e.IsHandled)
                MessageBox.Show(string.Format("ISO159296 Classes record count = {0}",
                        model.Classes.Count));

            base.OnButtonClickEventHandler(e);

        }
       
        #endregion
        #region METHOD: OnDALEventHandler
        /// <summary>
        /// Raises the <see cref="E:DALEventHandler"/> event.
        /// </summary>
        /// <param name="e">The <see cref="PrismContrib.Events.DALEventArgs"/>
        /// instance containing the event data.</param>
        public override void OnDALEventHandler(DALEventArgs e)
        {
            // We're only interested in IsLoading context property changes
            if (e.IsPropertyChange && e.PropertyName.Contains("IsLoading"))
            {
                if (!e.IsLoading)
                {
                    e.IsHandled = true;  // notify logging we handled it
                    base.OnDALEventHandler(e);
                    OnDataLoaded();
                }
            }
            else  // For logging purposes
                base.OnDALEventHandler(e);

        }
       
        #endregion       
        #region METHOD: OnModelPropertyChanged 

        /// <summary>
        /// Called when [model property changed].  Intended to be overridden
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/>
        /// instance containing the event data.</param>
        public override void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
#if !SILVERLIGHT
            // WPF/WinForms are using stub data so there is no callback method
            // to trigger OnDataLoaded() - we'll fire it manually on
            // ModelPropertyChanged
            if (e.PropertyName.Contains("Classes"))
                OnDataLoaded();
#endif
        }
       
        #endregion        

        #endregion
    }
}
 

Architectural note:  This application uses a combination of the Model-View-Presenter (MVP) pattern and the Model-View-ViewModel (MVVM) pattern, aka Presentation Model and Application Model.    By having multiple views share the same model (see Martin Fowlers Presentation Model) you can effectively share the same data without having to have a lot of complex logic to maintain state.   Each view can update the model and the other views will be notified via the observer pattern (INotifyPropertyChanged).

Trying to use MVVM alone has introduced the limitations that Martin Fowler discussed in THIS ARTICLE (paragraph above Figure 11).   As he suggest, it was the limitations that introduced the need for MVP.   Combining them gives us the best of both worlds.  

 


Tags: , , ,
Categories: WPF


Actions: E-mail | Permalink |  Grammar/Typo/Better way? Please let me know