Click here to Skip to main content
12,509,612 members (51,761 online)
Click here to Skip to main content
Add your own
alternative version

Stats

10K views
251 downloads
19 bookmarked
Posted

Rapid Development of Engineeging Web Site

, 1 Nov 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
From desktop to Web

Useful links

1. Introduction

At 2002 I have predicted that my software projects should have Web versions. Recently (October 2012) I have begun implement this idea. However during ten years I developed software such that it can be easily adopted for Web. So I once again
reinvented
The time machine. My idea of engineering web applications rapid development is similar to LabVIEW idea. LabVIEW supports graphical programming for both business logics and user interface.
 


LabVIEW Graphical Programming

My approach is the same. Following picture shows this fact.

Bisiness + UI

Desktop windows application enables us develop business logic. Then XAML designer yields interoperability of business logic with Silverlight user interface. So this soft contains desktop windows application and Web one. Both of them are developed for evaluation purposes only. Therefore Visual Studio Express 2012 for Windows Desktop
and Visual Studio Express 2012 for Web are used for development of these applications.   Evaluation Web client is a frame of the original article. Original article contains desktop application source code and other useful resources.

2. Background

Here we will consider general elements of business logic and corresponding Silverlight UI elements

2.1 Exception handling

A lot of software contains code which looks like

void z_error (char *m)
{ 
         fprintf(stderr, "%s\n", m); 
         exit(1); 
} 

or

 try
{
    // Do something ...
}
catch (Exception exception)
{
    MessageBox.Show(exception.Message);
}

Above code is not flexible and could not be easy used for Web applications. So I prefer construction which has a pure abstract exception handling and extension method.

     /// <summary>
    /// The error handler
    /// </summary>
    public interface IErrorHandler
    {
        /// <summary>
        /// Shows error
        /// </summary>
        /// <param name="exception">Exception</param>
        /// <param name="obj">Attached object</param>
        void ShowError(Exception exception, object obj);
 
        /// <summary>
        /// Shows message
        /// </summary>
        /// <param name="message">The message to show</param>
        /// <param name="obj">Attached object</param>
        void ShowMessage(string message, object obj);
    }     

 

         /// <summary>
        /// Shows exception (extension method)
        /// </summary>
        /// <param name="exception">Exception</param>
        /// <param name="obj">Attached object</param>
        static public void ShowError(this Exception exception, object obj = null)
        {
            if (errorHandler != null) // Static exception handler
            {
                errorHandler.ShowError(exception, obj);
            }
        } 

Now exception handling looks like

 try
{
    // Do something ...
}
catch (Exception exception)
{
      exception.ShowError();
}

Desktop applications can have following implementation of IErrorHandler interface

 void IErrorHandler.ShowError(Exception exception, object obj)
{
    MessageBox.Show(this, exception.Message);
}

However I prefer error handling with following UI

Error windows message

Web version of above error handling looks like:

<>
N Level Message Stack trace
1 10 NaN at EngineeringInitializer.BasicEngineeringInitializer.
2 10 NaN at Diagram.UI.StaticExtensionDiagramUI.Throw(Object o, Exception exception) at DataPerformer.DataConsumer.UpdateChildrenData() at DataPerformer.VectorFormulaConsumer.UpdateMeasurements()
3 10 NaN at Diagram.UI.StaticExtensionDiagramUI.Throw(Object o, Exception exception) at DataPerformer.DataConsumer.UpdateChildrenData() at DataPerformer.VectorFormulaConsumer.UpdateMeasurements()
4 10 NaN at Diagram.UI.StaticExtensionDiagramUI.Throw(Object o, Exception exception) at DataPerformer.DataConsumer.UpdateChildrenData() at DataPerformer.VectorFormulaConsumer.UpdateMeasurements()

This error handling has server and client tiers. Following code is implenentation of server tier.

Following code contains server side implementation
         /// <summary>
        /// List of exceptions
        /// </summary>
        private List<object[]> exceptions = new List<object[]>();
 
          
         #region IErrorHandler Menbers
 
        /// <summary>
        /// Shows error
        /// </summary>
        /// <param name="exception">Exception</param>
        /// <param name="obj">Attached object</param>
        public virtual void ShowError(Exception exception, object obj)
        {
            exceptions.Add(new object[] { exception, obj + "" });
            onException(exception, obj);
        }
 
        #endregion
 
        /// <summary>
        /// Creates error report
        /// </summary>
        /// <param name="doc">report doocument</param>
        public void CreateErrorReport(XmlDocument doc)
        {
            if (exceptions.Count != 0)
            {
                XmlElement el = doc.CreateElement("Exceptions");
                doc.DocumentElement.AppendChild(el);
                foreach (object[] o in exceptions)
                {
                    XmlElement e = doc.CreateElement("Exception");
                    el.AppendChild(e);
                    XmlElement eo = doc.CreateElement("Object");
                    eo.InnerText = o[1] + "";
                    e.AppendChild(eo);
                    XmlElement eex = doc.CreateElement("StackTrace");
                    e.AppendChild(eex);
                    eex.InnerText = (o[0] as Exception).StackTrace;
                    XmlElement em = doc.CreateElement("ExceptionMessage");
                    e.AppendChild(em);
                    em.InnerText = (o[0] as Exception).Message;
                    XmlElement et = doc.CreateElement("ExceptionType");
                    e.AppendChild(et);
                    et.InnerText = (o[0] as Exception).GetType() + "";
                }
                exceptions.Clear(); // Clears list of exceptions
            }
        }

Serer side collects exceptions on list, then information about exceptions is appended to XML document. Then document is sent to client.
Client side also contains own error handling interface:

     /// <summary>
    /// Shows errors
    /// </summary>
    public interface IErrorHandler
    {
        /// <summary>
        /// Shows errors
        /// </summary>
        /// <param name="x">Errors</param>
        void ShowErrorReport(XElement x);
    }
 

Every client object which implements this interface shows report about errors. Following code contains an implementation of this interface.

     /// <summary>
    /// HTML Error hander
    /// </summary>
    public partial class SilverlightControlHtmlErrorHandler : UserControl, IErrorHandler
    {
        #region Fields
 
        /// <summary>
        /// Id of html element
        /// </summary>
        string elementID;
 
        /// <summary>
        /// Name of script function
        /// </summary>
        string scriptFunction;
 
        #endregion
 
        #region Ctor
 
        /// <summary>
        /// Constructor
        /// </summary>
        public SilverlightControlHtmlErrorHandler()
        {
            InitializeComponent();
        }
 

        #endregion
 
        #region IErrorHandler Members
 
        void IErrorHandler.ShowErrorReport(XElement x)
        {
            XElement xe = x.Element("Exceptions");
            if (xe == null)
            {
                return;
            }
            IEnumerable<XElement> l = xe.Elements("Exception");
            // Creates table text
            string text = "<table border=\"1\"><tr><td>N</td><td>Level</td><td>Message</td><td>Stack trace</td></tr>";
            int i = 1;
            foreach (XElement e in l)
            {
                text += "<tr><td>" + i + "</td><td>" + e.Element("Object").Value + "</td><td>" + 
                    e.Element("ExceptionMessage").Value + "</td><td>" + e.Element("StackTrace").Value + "</td></tr>";
                ++i;
            }
            text += "</table>";
            Action<string> acttext = WriteText; // If script function does not exist
            if (scriptFunction != null)
            {
                if (scriptFunction.Length > 0)
                {
                    acttext = WriteScript; // If script function exists 
                }
            }
           Action act = () =>
                {
                    acttext(text);
                };
            Dispatcher.BeginInvoke(act);
 
        }
 
        #endregion
 
        #region Members
 
        #region Public
 
        /// <summary>
        /// Script Function
        /// </summary>
        public string ScriptFunction
        {
            get
            {
                return scriptFunction;
            }
            set
            {
                scriptFunction = value;
            }
        }
 

 
        /// <summary>
        /// Id of HTML output
        /// </summary>
        public string HtmlId
        {
            get
            {
                return elementID;
            }
            set
            {
                elementID = value;
            }
        }
 
        #endregion
 
        #region Private
 
        /// <summary>
        /// Writes inner HTML
        /// </summary>
        /// <param name="text">Text to write</param>
        void WriteText(string text)   
        {
            HtmlDocument doc = HtmlPage.Document;
            HtmlElement element = doc.GetElementById(elementID);
            if (element != null)
            {
                element.SetAttribute("innerHTML", text);
            }
        }
        
        /// <summary>
        /// Calls script function
        /// </summary>
        /// <param name="text">Argument of function</param>
        void WriteScript(string text)
        {
            HtmlPage.Window.Invoke(scriptFunction, text);
        }
 
        #endregion
 
        #endregion

This component shows exception report as inner HTML or calls script function. Following snippet contains example of error handler function.

  <script type="text/javascript">
 
      // Shows error 
      // text is error text
      function showScriptError(text) {
          var div_preview = document.getElementById("Errors");
          div_preview.innerHTML = text;
      }
</script>
 

<div id="Errors"></div>
 
Some Web client elements should be associated with error handlers. These elements implement following interface.
     /// <summary>
    /// Consumer of error handler
    /// </summary>
    public interface IErrorHandlerConsumer
    {
        /// <summary>
        /// Consumer of error handler
        /// </summary>
        IErrorHandler ErrorHandler
        {
            get;
            set;
        }
    }
 

Developer should not explicitly associate error handler. This operation is performed implicitly by following functions

         /// <summary>
        /// Recursive action
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="obj">Object</param>
        /// <param name="action">Action</param>
        public static void RecursiveAction<T>(this DependencyObject obj, Action<T> action) where T : class
        {
            if (obj is T)
            {
                action(obj as T);
            }
            int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(obj, i);
                if (child is DependencyObject)
                {
                    (child as DependencyObject).RecursiveAction<T>(action);
                }
            }
        }
 
        /// <summary>
        /// Gets root of objet
        /// </summary>
        /// <param name="element">The element</param>
        /// <returns>The root</returns>
        public static DependencyObject GetRoot(this DependencyObject element)
        {
            if (element is FrameworkElement)
            {
                DependencyObject dob = (element as FrameworkElement).Parent;
                if (!(dob is FrameworkElement))
                {
                    return element;
                }
                return GetRoot(dob);
            }
            return null;
        }
 
                              /// <summary>
        /// Root Recursive action
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="obj">Object</param>
        /// <param name="action">Action</param>
        public static void RootRecursiveAction<T>(this DependencyObject obj, Action<T> action)
            where T : class
        {
            DependencyObject d = obj.GetRoot();
            d.RecursiveAction<T>(action);
        }
                       /// <summary>
        /// Finds error handler
        /// </summary>
        /// <param name="consumer">Consumer of error handler</param>
        public static void FindErrorHandler(this ) consumer)
        {
            DependencyObject dob = consumer as DependencyObject;
            IErrorHandler eh = null;
            Action<IErrorHandler> find = (IErrorHandler h) =>
            {
                if (eh == null)
                {
                    eh = h;
                    return;
                }
            };
            dob.RootRecursiveAction<IErrorHandler>(find);
            if (eh != null)
            {
                consumer.ErrorHandler = eh;
            }
        } 

Following XAML code

<UserControl xmlns:SilverlightDiagramUI="clr-namespace:Diagram.UI.SilverlightDiagramUI;
    assembly=Diagram.UI.SilverlightDiagramUI" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DataPerformer.UI.SilverlightDataPerformer" 
    x:Class="DataPerformer.UI.SilverlightDataPerformer.SilverlightControlChartInputOutput"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"><StackPanel>
         <Grid x:Name="LayoutRoot" ShowGridLines="True">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
             <SilverlightDiagramUI:SilverlightControlAlias x:Name="Aliases" Grid.Column="0" 
             Grid.Row="0"  HorizontalAlignment="Stretch" Margin="0,0,0,0" />
            <local:SilverlightControlHtmlOutput x:Name="Chart"  Grid.Column="0" Grid.Row="1" />
            <Button click="Button_Click" content="Start" grid.row="2" 
            horizontalalignment="Stretch" margin="0,0,0,0" width="75" x:name="Start"/>
            <local:SilverlightControlHtmlErrorHandler x:Name="Errors" 
            HorizontalAlignment="Left" Height="0" Grid.Row="2" Width="0" Visibility="Collapsed"/>
        </Grid>
    </StackPanel>
</UserControl>

contains one IErrorHandler (it is SilverlightControlHtmlErrorHandler) and two IErrorHandlerConsumer objects (SilverlightControlAlias and SilverlightControlHtmlOutput. Both consumers automatically associated with error handler because all three elements have common root. Following picture represents interoperability between IErrorHandler and IErrorHandlerConsumer. Following sequence diagram explains usage of exception handler.

Error handler sequence

2.2 Client interfaces

2.2.1 Initialization interface

Some UI elements should be initialized after their appearance. Following schema represents such initialization

Initialization sequence

All objects which require such initialization implement IPostLoadService interface.

2.2.2 Additional information interface

A lot of user controls contains child controls. Usually request for server contains information which is contained in all children. Following control

Children controls

has two children. "Green" child generates following XML

<Aliases Desktop="Exponent.cfa">
   <Alias>
     <Name>Exp.a</Name>

"Blue" component generates:

<ChartDocument Chart="ExponentChart.xml" Desktop="Exponent.cfa">
  <Parameters>
    <Start>0</Start>
In result following XML is sent to server:
<ChartDocument Chart="ExponentChart.xml" Desktop="Exponent.cfa">
  <Aliases Desktop="Exponent.cfa">
    <Alias>
      <Name>Exp.a</Name>

So information from "blue" control is appended to information of "green" control. "Blue control implements following interface

     /// <summary>
    /// Adds information to request
    /// </summary>
    public interface IAddRequest
    {
        /// <summary>
        /// Adds request
        /// </summary>
        /// <param name="x">Request element</param>
        void Add(XElement x);
    }

All IAddRequest objects add information to request. Following diagram illustrates logics of request:

IAddRequest Business Logic

Note that add request operation should not be called explicitly. It is called implicitly by following function

        /// <summary>
        /// Adds information for request
        /// </summary>
        /// <param name="obj">Object</param>
        /// <param name="x">Request Xml</param>
        public static void AddRequest(this DependencyObject obj, XElement x)
        {
            Action<IAddRequest> action = (IAddRequest a) => { a.Add(x);};
            obj.RootRecursiveAction<IAddRequest>(action);
        }

2.3 Constant input

As rule every engineering task have input constants. For example calculation of f(t)=aebt function has two parameters a and b which can be regarded as constants. Software should support constant input.

2.3.1 Business logic

Following picture represents business logic of above task.

Simple Business Logic

Where Exp component calculates f(t)=aebt. Whole above picture is named Desktop. Any desktop correspond to object which implemens IDesktop interface. Properties of Exp are presented below.

Exp properties

Bottom right part of above window contains editor of a and b constants. The Exp object implements following interface:

     /// <summary>
    /// Collection on named data units
    /// </summary>
    public interface IAlias : IAliasBase
    {
        /// <summary>
        /// Names of all data units
        /// </summary>
        IList<string> AliasNames
        {
            get;
        }
 
        /// <summary>
        /// Access to data unit by name
        /// </summary>
        object this[string name]
        {
            get;
            set;
        }
 
        /// <summary>
        /// Gets unit type
        /// </summary>
        /// <param name="name">Unit name</param>
        /// <returns>Type of unit</returns>
        object GetType(string name);
    }

Following code explains usage of this interface.

IAlias alias = Exp as IAlias;
IList<string> names = alias.AliasNames; // {"a", "b"};
object a = alias["a"]; // a = 0.7 {double}
object b = alias["b"]; // a = 0.3 {double}
object t = alias.GetType("a"); // t = (double)0;
double x = 0.8;
alias["a"] = x;
a = alias["a"]; // a = 0.8 {double}

Aliases can be accessed from desktop by following way.

IDesktop desktop = ...;
object a = desktop.GetAliasValue("Exp.a"); // a = 0.7 {double}
object t = desktop.GetAliasType("Exp.a");  // t = (double)0;
double x = 0.8;
desktop.SetAliasValue("Exp.a", x);
a = desktop.GetAliasValue("Exp.a");         // a = 0.8 {double}

Also one can set aliases by following function

        /// <summary>
        /// Set aliases of desktop
        /// </summary>
        /// <param name="desktop">Desktop</param>
        /// <param name="document">Document</param>
        public static void SetAliases(this IDesktop desktop, XmlDocument document)

If document (second argument of above function) contains following element:

<Aliases Desktop="Exponent.cfa">
  <Alias>
    <Name>Exp.a</Name>

then call of this function is equivalent to following code:

desktop.SetAliasValue("Exp.a", (double)0.7);
desktop.SetAliasValue("Exp.b", (double)0.3);

Business of constant input is call of SetAliases function with proper arguments.

2.3.2 Client component

Following user control implicitly performs remote call SetAliases. The term "implicitly" means that this task is not independent. Setting of aliases every time is subtask of major task.

     /// <summary>
    /// Alias control
    /// </summary>
    public partial class SilverlightControlAlias : UserControl, IPostLoadService, 
            IAddRequest, IErrorHandlerConsumer

It has following key properties

<SilverlightDiagramUI:SilverlightControlAlias Desktop="Exponent.cfa" Aliases="ExponentAliases.xml"  />

Where Desktop="Exponent.cfa" (resp. Aliases="ExponentAliases.xml") is file name of desktop (resp. aliases). As IPostLoadService it sends following initialization request.

<Aliases Desktop="Exponent.cfa" Aliases="ExponentAliases.xml" />

From content of files "Exponent.cfa", ExponentAliases.xml" server generates following response

<Aliases>
    <Item>
        <Number>1</Number>

Then user control receives initialization response and its appearance is changed by following way:

UI initialization


This component implements IAddRequest interface. So it provides information about values of aliases for major task. Additional information  is considered in 2.2.2.



2.3 Output of chart

2.3.1 Business logic

Our sample

SimpleBusinessLogic

contains Chart component. Besides graphical mode it has text one.

Chart text mode

This mode generates following XML:

<Root>
  <Parameters>
    <Parameter Name="Value" Value="0.7" />
    <Parameter Name="Argument" Value="0" />
  </Parameters>

This XML is returned by following function

        /// <summary>
        /// Creates Xml document
        /// </summary>
        /// <param name="desktop">Desktop</param>
        /// <param name="input">Input</param>
        /// <returns>Document</returns>
        static public XmlDocument CreateXmlDocument(this IDesktop desktop, XmlDocument input)

where input is represented by following text.

  <ChartDocument>
        <Interval>
            <Start>0</Start>
            <Step>0.01</Step>
            <Finish>0.03</Finish></Interval>
     <ChartName>Chart</ChartName>
     <Parameters>
         <Parameter>
             <Name>Exp.Formula_1</Name>
             <Value>Value</Value>
        </Parameter>
         <Parameter>
             <Name>Exp.Formula_2</Name>
             <Value>Argument</Value>
         </Parameter>
     </Parameters>
</ChartDocument>
 

License

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

Share

About the Author

Petr Ivankov
Architect
Russian Federation Russian Federation
Ph. D. Petr Ivankov worked as scientific researcher at Russian Mission Control Centre since 1978 up to 2000. Now he is engaged by Aviation training simulators http://dinamika-avia.com/ . His additional interests are:

1) Noncommutative geometry

http://front.math.ucdavis.edu/author/P.Ivankov

2) Literary work (Russian only)

http://zhurnal.lib.ru/editors/3/3d_m/

3) Scientific articles
http://arxiv.org/find/all/1/au:+Ivankov_Petr/0/1/0/all/0/1

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
D V L30-Sep-15 19:37
professionalD V L30-Sep-15 19:37 
QuestionMy Vote of 5 Pin
Manikandan102-Jun-14 21:52
professionalManikandan102-Jun-14 21:52 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160929.1 | Last Updated 1 Nov 2012
Article Copyright 2012 by Petr Ivankov
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid