ASP.NET tutorial

This tutorial describes how you can provide and consume Qworum services with ASP.NET MVC3 (C#).

The web application presented here is available at GitHub.

Browse the documentation for in-depth information about Qworum.

Note: This tutorial is somewhat out of date, because it makes intensive use of XML messaging which needlessly increase the traffic between web browsers and servers.
Instead, we recommend the use of JavaScript and HTML bindings for implementing and calling interactive services. Also, XML capability is not available on all Qworum browser add-ons.

Boilerplate

In order to use Qworum, your application only needs to be able to:

To start with, implement utility methods for reading XML documents:

// Controllers/HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Web.Helpers;

namespace HelloWorld.Controllers {
    public class HomeController : Controller {
        // ...


        // Utilities

        protected XNamespace NS = "http://qworum.net/";

        protected XmlDocument ParseXMLPost(HttpRequestBase request) {
            XmlDocument doc = null;
            if (request.ContentType.IndexOf("application/xml") == 0) {
                using (var reader = new StreamReader(request.InputStream)) {
                    try {
                        doc = new XmlDocument();
                        string xml = reader.ReadToEnd();
                        doc.LoadXml(xml);
                    } catch (XmlException ex) {
                        doc = null;
                    }
                }
            } else if (request.ContentType.IndexOf("application/x-www-form-urlencoded") == 0) {
                try {
                    doc = new XmlDocument();
                    FormCollection form = new FormCollection(Request.Unvalidated().Form);
                    string xml = form.Get("qworum");
                    doc.LoadXml(xml);
                } catch (Exception ex) {
                    doc = null;
                }
            }
            return doc;
        }

        protected object EvaluateXPath(XmlDocument doc, string xpath) {
            try {
                XPathDocument xpathDoc = new XPathDocument(new XmlNodeReader(doc));
                XPathNavigator nav = xpathDoc.CreateNavigator();
                XPathExpression expr = XPathExpression.Compile(xpath);
                return nav.Evaluate(expr);
            }catch(Exception ex){}
            return null;
        }
    }
}

Providing a Qworum service

Implement a service that receives a call argument, computes a result, shows the result to the end-user, and returns the result.

Best practice is to use one controller per Qworum service, and one action for each service phase. Here is a controller for that service:

// Controllers/ServiceController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using System.Xml;
using System.Xml.Linq;
using System.Web.Helpers;

namespace HelloWorld.Controllers {
    public class ServiceController : HomeController {

        /* Phase 1:
         * Receive call argument, compute result
         */
        [HttpPost]
        [ValidateInput(false)]
        new public XDocument Index() {
            // input
            string name = (string)EvaluateXPath(ParseXMLPost(Request), "string(/*/text())");
            // output
            string sentence = "Hello, "+name;
            Response.ContentType="application/xml";
            return new XDocument(
                new XElement(NS+"goto", new XAttribute("href", "Show"),
                    new XElement(NS+"variable", new XAttribute("name", "result"),
                        new XElement("sentence", sentence)
                    )
                )
            );
        }

        /* Phase 2:
         * Show result to end-user
         */
        public ActionResult Show() {
            XmlDocument doc = ParseXMLPost(Request);
            ViewBag.Sentence = (string)EvaluateXPath(doc, "string(/*/text())");
            return View();
        }

        /* Phase 3:
         * Return result
         */
        public XDocument Result() {
            Response.ContentType="application/xml";
            return new XDocument(
                new XElement(NS+"variable", new XAttribute("name", "result"))
            );
        }
    }
}

And here is a view for phase 2 of that service:

<!-- Views/Service/Show.cshtml -->
<h1>Qworum Service say_hello</h1>
<p>The current call will return: @ViewBag.Sentence</p>
<p style='font-size: 70%'>
  <a href="Result">Click to return</a>
</p>

Phase 3 is unnecessary if the browser provides a qworum JavaScript object. So phase 2 can be rewritten as follows in order to reduce server workload:

<!-- Views/Service/Show.cshtml -->
<h1>Qworum Service say_hello</h1>
<p>The current call will return: @ViewBag.Sentence</p>
<button id='return'>Return</button>

<script>
  var button = document.getElementById('return');
  button.addEventListener('click', function(){
      evaluateQworumMessage(['variable','result'], 'Result');
  });

  function evaluateQworumMessage(msg, msgUrl){
    if(typeof qworum == 'object')if(typeof qworum.eval == 'function'){
      qworum.eval(msg);
      return;
    }
    window.location = msgUrl;
  }
</script>

Consuming a Qworum service

In order to call the service above, your site needs to generate a Qworum message that contains a call instruction. Implement that with an action in a second controller, as follows:

// Controllers/HomeController.cs

// ...
namespace HelloWorld.Controllers {
    public class HomeController : Controller {
        // ...

        public XDocument CallService() {
            Response.ContentType="application/xml";
            return new XDocument(
                new XComment("\n"+
                  "  QWORUM SUPPORT IS MISSING IN YOUR BROWSER:\n"+
                  "  This site only works correctly with Qworum enabled browsers. \n"+
                  "  Please visit http://www.qworum.com/products\n"
                ),
                new XElement(NS+"goto", 
                    new XAttribute("href", "ReceiveCallResult"),
                    new XElement(NS+"call", 
                        new XAttribute("href", "/Service/Index"),
                        new XElement("name", "Dave")
                    )
                )
            );
        }
        // ...
    }
}

It is good practice to include a comment such as the one above in Qworum messages generated by web applications. This is obviously not needed for messages generated by Qworum services.

The goto instruction in that message specifies the URL where the call result is to be posted. Here is a view for that URL:

<!-- Views/Home/ReceiveCallResult.cshtml -->
<h1>Application</h1>
<p>Result received from say_hello service is:  @ViewBag.Sentence </p>
<p style='font-size: 70%'><a href="Index">Go to home page</a></p>

And here are the Index and CallService actions:

// Controllers/HomeController.cs

// ...
namespace HelloWorld.Controllers {
    public class HomeController : Controller {
        // ...

        // Start page
        public ActionResult Index() {
            return View();
        }

        [HttpPost]
        [ValidateInput(false)]
        public ActionResult ReceiveCallResult() {
            XmlDocument doc = ParseXMLPost(Request);
            ViewBag.Sentence = (string)EvaluateXPath(doc, "string(/*/text())");
            return View();
        }
        // ...
    }
}

User experience

You should observe the following: