In this tutorial, we'll learn how to build a simple Magic 8-ball service. Its purpose is to return a random answer whenever it gets invoked. Usage is very straightforward: ask a question out loud, then enter the 8-balls' URI into your favorite browser, and get the answer you were looking for!
This tutorial shows:
Let's begin by creating a new project. Or you can dowload the source files below!
The first thing we need to do is add a reference to mindtouch.dream.dll. This is the assembly where all the required classes are defined. Then we need to add a using statement to our source file.
using MindTouch.Dream;
Now, let's create a namespace for it. We'll also define Yield as a shorthand.
namespace MindTouch.Dream.Samples {
using Yield = System.Collections.Generic.IEnumerator<IYield>;
}
OK. That's it! We're ready to write our first service.
A Dream service can be written one of two ways: the hard way or the easy way. The hard way would be to derive our service class from IDreamService and implement every method and property by hand. The easy way is to derive our service class from DreamService, which implements all the necessary default behavior. Let's do the latter!
public class EightBallService : DreamService {
}
Voilá! We now have a Dream service. Well, almost. We're missing one crucial part: it's blueprint. A blueprint is an XML-based description of the service used by the Dream runtime to manage it. It contains information about the name, type, copyright, features, and where to find documentation about it. Again, we are faced with making a difficult choice: either we create the XML document by hand, or we use attributes to have the Dream runtime generate it for us on the fly. We already took once the easy way out, so doing it again won't really matter. Attributes it is!
[DreamService("Dream Tutorial 8-Ball", "Copyright (c) 2006, 2007 MindTouch, Inc.",
Info = "http://doc.opengarden.org/Dream/Samples /8-Ball",
SID = new string[] { "http://services.mindtouch.com/dream/tutorial/2007/03/8ball" }
)]
The DreamService attributes has four parts of which only the first two are mandatory:
A blueprint contains a lot more information than just these four fields, but the rest is generated through reflection and other attributes.
Finally, we need to add a feature to our service. "Feature" is the word used in Dream to describe a service entry point, similar to a method on an object. A feature is defined by its pattern, which captures two parts:
Both parts are combined by a colon, resulting in a pattern that might look like "GET:collection/{item}". Each feature may have one or more patterns. The blueprint of a service lists all its features. Since we didn't create a blueprint document, we'll use the DreamFeature attribute to specify that our method is a feature.
[DreamFeature("GET:", "Returns a random 8-ball message")]
public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
}
The DreamFeature attribute has two parts:
The method associated with the feature must return an enumerator and take exactly three arguments: a DreamContext, a DreamMessage, and a TaskYield<DreamMessage>. The enumerator return is automatically generated by the compiler when using the yield keyword, such as yield break. The DreamContext holds information specific to the current request. The DreamMessage contains information sent by the client. The TaskYield<DreamMessage> is a communication conduit to respond to the client.
Ok, it's time to add the Magic 8-Ball behavior to our code. For this, we'll create two static member variables: _random and _answers.
private static Random _random = new Random();
private static string[] _answers = new string[] {
"Reply hazy, try again", "Without a doubt", "My sources say no", "As I see it, yes"
};
The first one (_random) allows us to generate random numbers.
The second one (_answers) captures our list of possible answers.
Last, we need to change our GetAnswer method to pick a random answer from the list and send it back.
public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
// compute a random number between 0 and the number of responses we have
int index = _random.Next(_answers.Length);
// send our response back to the requestor
DreamMessage message = DreamMessage.Ok(MimeType.TEXT, "The 8-ball says: " + _answers[index]);
response.Return(message);
yield break;
}
Let's look at these lines in a little bit more detail:
That's it! We now have put in place all the piece required to have a functional service.
Let's recap and see what it looks like all put together,
namespace MindTouch.Dream.Samples {
using Yield = System.Collections.Generic.IEnumerator<IYield>;
[DreamService("Dream Tutorial 8-Ball", "Copyright (c) 2006, 2007 MindTouch, Inc.",
Info = "http://doc.opengarden.org/Dream/Samples/8-Ball",
SID = new string[] { "http://services.mindtouch.com/dream/tutorial/2007/03/8ball" }
)]
public class EightBallService : DreamService {
private static System.Random _random = new System.Random();
private static string[] _answers = new string[] {
"Reply hazy, try again", "Without a doubt", "My sources say no", "As I see it, yes"
};
[DreamFeature("GET:", "Returns a random 8-ball message")]
public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
response.Return(DreamMessage.Ok(MimeType.TEXT, "The 8-ball says: " + _answers[_random.Next(_answers.Length)]));
yield break;
}
}
}
We're ready to compile the service and try it out. No additional coding is needed.
Compile the service into a .dll from the .cs file using the csproj file or Makefile and copy over the files from your dream/redist folder.
Finally, let's run our service. For this, we need to do two things:
Fortunately, we don't need to program to do this. We only need to create an XML script and pass it to mindtouch.host.exe.
Let's create an XML script called 8ball.startup.xml with the following contents:
<script>
<!-- register the blueprint for our 8-ball service -->
<action verb="POST" path="/host/load?name=dream.sample.8ball" />
<!-- instantiate it -->
<action verb="POST" path="/host/services">
<config>
<path>8ball</path>
<sid>http://services.mindtouch.com/dream/tutorial/2007/03/8ball</sid>
</config>
</action>
</script>
The purpose of the script is quite straightforward. Each <action> element represents a web-request. The first child node of the <action> element becomes the body of the request. Hence, the effect of the above script could also be produced by using Plug or curl, a popular command line tool for doing HTTP requests.
Now, let's start our service:
mindtouch.host.exe script 8ball.startup.xml
The 8-ball service is now running and can be accessed via any browser: