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

How to Harness the Power of XHTML and XForms in your .NET Applications

By , 11 Feb 2009
 
Screenshot

Introduction

XForms is an important recommendation from the W3C that enables complex XML-handling applications to be defined in a simple, declarative syntax. This article demonstrates how you can harness this technology in your own C# applications. However, the same techniques may be easily applied to other .NET languages, or indeed any programming language that has a COM binding. The XHTML and XForms rendering power at the heart of it all will be provided by Ubiquity formsPlayer, a free open-source XForms processor.

Why XForms?

The advantages of XForms over other solutions have been argued far more eloquently elsewhere than I could manage, so I won't go into great detail here. Instead, those of you interested in reading around the subject in more depth are directed to the links that appear at the foot of this article. To summarise some of the most salient points, however:

  • XForms documents are device- and platform-independent.
  • XForms uses a declarative syntax, which aids faster development and reduces the potential for bugs when compared to more traditional, procedural approaches.
  • XForms separates data, user interface, and application logic according to the model-view-controller (MVC) design pattern.
  • XForms is an open standard, which means that the same document may be deployed unmodified across all conforming processors.
  • XForms documents are inherently accessible by design.
  • XForms enables comprehensive input validation at the point of entry, reducing the need for server round trips in distributed web applications.

Getting Started

Before you can begin, you will need to install Ubiquity formsPlayer, which is a free and a reasonably small download. Additionally, you'll also need to separately install the Ubiquity Browser eXtensions to get the embeddable Renderer component. Once both of these have been installed, you must import two COM libraries into your C# project via the 'Add Reference' dialog:

  • [Program Files]\Common Files\UBX\Renderer\Renderer5.dll
  • [Program Files]\Common Files\UBX\DOM\DOM2Events.dll

The first of these libraries, the Renderer, provides a wrapper for all of the XHTML and XForms rendering functionality. The second component, an implementation of the W3C's DOM Level 2 Events recommendation, facilitates communication between your application, the Renderer, and the live document.

Instantiating the Renderer

Firstly, you must add a new class to your application, representing the window that will host the Renderer. In the example code, this class is called RenHost. The constructor for this class needs to instantiate a copy of RenderLib.RendererClass, storing a pointer to the instance's IRender interface:

public class RenHost : Form {
    private RenderLib.IRender renderer;

    ...

    public RenHost()
    {
        InitializeComponent();

        this.components = new System.ComponentModel.Container();
        this.components.Add(this.buttonOpen);
        this.components.Add(this.labelUrl);
        this.components.Add(this.textboxUrl);
        this.components.Add(this.panelRenderer);

        this.createRenderer();
    }

    private void createRenderer()
    {
        this.renderer = (RenderLib.IRender)new
RenderLib.RendererClass();
    }

    ...
}

Next, this.renderer must be made a child of a Panel on your RenHost class. This is achieved by calling Initialise() and passing in the Panel's handle as a parameter, like so:

private void createRenderer() {
    this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
    this.renderer.Initialise(null,
(int)this.panelRenderer.Handle);
}

Dispatching Events

Your application may now customise various characteristics of this.renderer, such as setting its initial size or instructing it to delegate any navigation requests to your application. These properties and many others are all set by dispatching events to this.renderer's IEventTarget interface.

To simplify this process, add a new method to your class along the following lines:

private void dispatchEvent(String type, int arg1, int arg2, String arg3) {
    DOM2EventsLib.IRendererEvent rendererEvent = 
	(DOM2EventsLib.IRendererEvent)new DOM2EventsLib.RendererEvent();
    rendererEvent.initRendererEvent(type, false, false, arg1, arg2, arg3);

    DOM2EventsLib.IEventTarget eventTarget = (DOM2EventsLib.IEventTarget)this.renderer;
    eventTarget.dispatchEvent((DOM2EventsLib.DOMEvent)rendererEvent);
}

It is not necessary to worry about what all of the different parameters mean at this point. Simply know that the code above allows you to dispatch a renderer event of the specified type, along with some event-specific contextual parameters.

Initialising your Renderer Instance

The first thing that you will want to do with this new method, then, is to inform this.renderer of its initial size and position:

private void createRenderer() {
    this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
    this.renderer.Initialise(null, (int)this.panelRenderer.Handle);

    this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, "");
    this.dispatchEvent("renderer-set-position", 0, 0, ""); }

You also need to ensure that this.renderer gets notified of any resizing that occurs in the parent window. This requires you to add a handler for the Resize event:

private void panelRenderer_Resize(object sender, EventArgs evt) {
    this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, ""); }

Once these initial window-related properties have been set, you must instruct this.renderer to take active ownership of its Panel:

private void createRenderer() {
    this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
    this.renderer.Initialise(null, (int)this.panelRenderer.Handle);

    this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, "");
    this.dispatchEvent("renderer-set-position", 0, 0, "");
    this.dispatchEvent("renderer-activate", 0, 0, "");
}

Rendering Documents

Your renderer instance is now initialised and ready to display your documents. This, in itself, is a two stage process. First, the document must be loaded in one of two ways:

  • By passing a string representation of the markup to LoadFromString()
  • By passing a Microsoft IXMLDOMDocument interface pointer to LoadFromDOM()

Regardless of the method that you use, the document must be well-formed, otherwise the load will fail; malformed documents may only be rendered using the supplementary RenderUnmodified() and NavigateUnmodified() methods. After the document has been loaded successfully, it can be rendered by calling the Render() method. You may also pass a fragment identifier to this method, if necessary.

So, based on the above, a set of methods along the following lines would enable a browser-style application to display a document entered by the user:

private void loadDocument() {
    XmlDocument document = new XmlDocument();
    Uri uri = this.getUri();
    try
    {
        document.Load(uri.AbsoluteUri);
        this.renderer.LoadFromString(document.OuterXml,
this.getBase(uri));
        this.renderer.Render(this.getFragmentId());
    }
    catch (XmlException exception)
    {
        MessageBox.Show(this, "Failed to parse " + exception.SourceUri
+ "\n\nReason: " + exception.Message + "\nLine: " +
exception.LineNumber + "\nColumn: " + exception.LinePosition +
"\nSource: " + exception.Source, "RenHost XML parse error");
    }
    catch (COMException exception)
    {
        MessageBox.Show(this, "The renderer component encountered an error\n" + 
			exception.StackTrace, "RenHost error");
    }
}

private Uri getUri()
{
    if (this.getFragmentIndex() != -1)
        return new Uri(this.textboxUrl.Text.Substring(0,
this.getFragmentIndex()));

    return new Uri(this.textboxUrl.Text); }

private String getBase(Uri uri)
{
    return uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.LastIndexOf('/') + 1); }

private String getFragmentId()
{
    if (this.getFragmentIndex() != -1)
        return this.textboxUrl.Text.Substring(this.getFragmentIndex() + 1);

    return "";
}

private int getFragmentIndex()
{
    return this.textboxUrl.Text.IndexOf('#');
}

Clearing Up

You can continue to load and render as many documents after this as you wish. All that remains is to ensure that you call the Destroy() method before your application terminates:

protected override void Dispose(bool disposing) {
    this.destroyRenderer();

    if (disposing && this.components != null)
    {
        this.components.Dispose();
    }

    base.Dispose(disposing);
}

private void destroyRenderer()
{
    this.renderer.Destroy();
    this.renderer = null;
}

Links

History

  • 09-Oct-2007: Created
  • 13-Nov-2007: Added separate download information for the Renderer component, pending resolution of an issue in the formsPlayer installer
  • 05-Dec-2007: Switched license to CDDL, and made some minor cosmetic changes
  • 11-Feb-2009: Updated code and download details to use latest versions of formsPlayer and UBX

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

About the Author

Phil Booth
Software Developer webBackplane
United Kingdom United Kingdom
Member
I'm a Software Engineer, with a background in systems programming using C. In more recent years I have been involved with applications programming, initially using C++ and Delphi, but most recently working with JavaScript and XForms to deliver applications via the web browser. In my spare time I like to play with lots of cool languages that my job doesn't permit me to. Currently, I'm enjoying getting to grips with Erlang.

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 1memberNaso6417 Jan '11 - 22:34 
Really bad example for Code project article! Nothing is working. Even the example file can not be opened.
GeneralRe: My vote of 1memberphilbooooooo21 Oct '11 - 5:08 
Thanks very much for your constructive criticism. The code is old and has not been maintained for over two years. However, it is open source and anyone intelligent and motivated should find it quite straightforward to solve any issues.
QuestionI Have a Questionmemberm.shahrouri22 Feb '09 - 11:10 
Would you recommend a good book to read about Xforms?
AnswerRe: I Have a QuestionmemberPhil Booth24 Feb '09 - 9:51 
Unfortunately, the only two XForms books that I know of came out quite a few years ago, to coincide with the first edition of the XForms 1.0 recommendation. Since then, a lot of improvements have been made to the spec, so the best information that I'm aware of is online. As well as the links I mention in the article, some of these may be of interest to you:
 
Why XForms Matter, Revisited[^]
 
Understanding XForms: The Model[^]
 
Understanding XForms: Components[^]
 
XForms for HTML Authors[^]
 
Orbeon Forms Tutorial[^]
 
XForms Tutorial and Cookbook[^]
Questionevent-based communication between the document and the appmemberabel_b15 Apr '08 - 6:11 
I am playing around with the renderer and found that I need some events be catched at the host app.
Is thre any sample code to illustrate event-based communicaction between the doc level and the host app?
TIA.
AnswerRe: event-based communication between the document and the app [modified]memberPhil Booth15 Apr '08 - 22:45 
Hi Abel,
 
I've not had time to write this next bit up as a proper tutorial, but hopefully the following code snippets and so on should help you get it working. If you want more information about the different event types and their arguments, I recommend inspecting DOM2Events.dll with the MS OLE/COM Object Viewer.
 
To fire events out of the rendered document to the host application, some script along the following lines should do the trick:
 
// Dispatch a simple event with no arguments.
function dispatchDOMEvent(type)
{
    var evt = new ActiveXObject("DOM2Events.DOMEvent");
    evt.initEvent(type, true, false);
    document.EventTarget.dispatchEvent(evt);
 
    // Returning an empty string allows the function
    // to be used on the rhs from xf:setvalue.
    return "";
}
 
// Dispatch an event with up to three arguments: number; number; string.
function dispatchRendererEvent(type, arg1, arg2, arg3)
{
    var evt = new ActiveXObject("Renderer.RendererEvent");
    evt.initEvent(type, true, false, arg1, arg2, arg3);
    document.EventTarget.dispatchEvent(evt);
 
    // Returning an empty string allows the function
    // to be used on the rhs from xf:setvalue.
    return "";
}
 
Then call these methods as and when you need to dispatch events up to your application. If you want to trigger this from one of formsPlayer's XForms controls, you first need to define the following namespace on the html element:
 
xmlns:js="http://www.formsplayer.com/inlineScript"
 
Then you might invoke the event dispatch like this:
 
<xf:trigger>
    <xf:label>
        Dispatch
    </xf:label>
 
    <xf:setvalue ref="/foo/bar" value="js:dispatchRendererEvent('my-event', 6, 9, '42')" />
</xf:trigger>
 
Obviously, if you just want to dispatch an event from vanilla HTML+JavaScript, it is not necessary to jump through this hoop.
 
To handle the event in your application, you'll need some code resembling the below (assuming your app is written in C#):
 
public void handleEvent(DOMEvent evt)
{
    switch(evt.type)
    {
        case "my-event":
            // TODO: Insert your event handling code here.
            break;
        ...
    }
}
 
Lastly, you need to ensure that you register as a listener for the events that you are interested in, otherwise your handleEvent() will not get called. Additionally, to ensure sane destruction of the renderer and formsPlayer, you need to unregister as a listener for those events before you call Destroy():
 
private void init(void)
{
    ...
 
    IEventTarget tgt = (IEventTarget)this.m_Renderer;
    tgt.addEventListener("my-event", (EventListener)this, 0);
}
 
private void destroy(void)
{
    IEventTarget tgt = (IEventTarget)this.m_Renderer;
    tgt.removeEventListener("my-event", (EventListener)this, 0);
 
    ...
 
    this.m_Renderer.Destroy();
 
    ...
}
 
That should be everything you need to know. The code above is all written from memory and has not been tested, so there may be mistakes. Let me know if you have any problems, they should be straightforward to resolve.
 
Cheers,
Phil.
 
modified on Wednesday, April 16, 2008 5:19 AM

GeneralRe: event-based communication between the document and the appmemberabel_b16 Apr '08 - 9:29 
Thank you very much, Phil, for your help.
I could successfully run an event across the doc limits into the host app using the dispatchDOMEvent() javascript function, that uses ActiveXObject("DOM2Events.DOMEvent").
But failed to use the several args alternative.
It reports 'Automation server cannot create the object' when executing the following line:
var evt = new ActiveXObject("Renderer.RendererEvent");
What's wrong?
On the other side, what should be the handler code (on the app side) to get the 3 args sent by the javascript?
TIA.
 
abel
GeneralRe: event-based communication between the document and the appmemberPhil Booth17 Apr '08 - 0:19 
Hi Abel,
 
I'd normally guess that the error means DOM2Events.dll is not registered, but that is clearly not the case here since everything else is working okay. I'll see if I can reproduce the problem and debug what's going wrong, however I probably won't have time to do this until the weekend.
 
To extract the arguments from the renderer event object in your C# app, you might use some code that looks like this:
 
public void handleEvent(DOMEvent evt)
{
    switch(evt.type)
    {
        case "my-event":
            IRendererEvent re = (IRendererEvent)evt;
            OnMyEvent(re.numberParameterOne, re.numberParameterTwo, re.stringParameter);
            break;
        ...
    }
}
 
I definitely recommend inspecting DOM2Events.dll with the MS OLE/COM Object Viewer if you want to know more about all of the objects, interfaces, properties and methods that are available.
 
I'll post back here when I've had a chance to look at the automation error.
 
Cheers,
Phil.
GeneralRe: event-based communication between the document and the appmemberabel_b17 Apr '08 - 7:08 
Thank you, Phil, for your valuable help.
The code that I got working is the following:
 
1.- On the doc side:
 
// Dispatch an event with up to three arguments: number; number; string.
function dispatchRendererEvent(type, integerArg1, integerArg2, stringArg3)
{
var evt = new ActiveXObject("DOM2Events.RendererEvent");
evt.initRendererEvent(type, true, false, integerArg1, integerArg2, stringArg3);
document.EventTarget.dispatchEvent(evt);
 
// Returning an empty string allows the function
// to be used on the rhs from xf:setvalue.
return "";
}
 
2.- On the app side:
 
public void handleEvent(DOMEvent evt)
{
switch(evt.type)
{
case "my-event":
int arg1, arg2;
string arg3;
IRendererEvent re = (IRendererEvent)evt;
re.longParams(out arg1, out arg2);
arg3 = re.strParam;
OnMyEvent(arg1, arg2, arg3);
break;
...
}
}
 
So this subject is finished this way.
Two other problems arised in the meantime when playing around with the project downloaded from the article:
 
1.- The code that reads the XML document (xHTML actually) is as follows:
 
XmlDocument doc = new XmlDocument();
doc.Load(sURL);
this.Renderer.LoadXML(doc.OuterXml);
 
If the document is not well-formed an exception is thown by LoadXML, but I could not catch that exception by no try-catch combination.
Is there something special with that exception?
 
2.- When exiting the app a horrible .NET-BrodcastEventWindow Application Error window appears reporting an invalid memory access, but only if a document is rendered, if not the app closes gracefully.
Is this something related to the renderer?
Is this happenning to somebody else?
 
Thanks again for your help.
 
abel
GeneralRe: event-based communication between the document and the appmemberPhil Booth18 Apr '08 - 0:29 
Hi Abel,
 
Any exceptions that are emitted from renderer methods have been converted from COM HRESULT error codes by the .NET framework. I believe this means that they must be caught as type COMException[^], however I'm not a C#/.NET guru so that is worth looking into properly.
 
The crash on closing your application is an issue that I believe has been fixed for a formsPlayer customer. I'll update the installer for the Renderer component later on today and you can see if it cures your problem.
 
Cheers,
Phil.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 11 Feb 2009
Article Copyright 2007 by Phil Booth
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid