|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIf you have read my previous article Scalable COMET Combined with ASP.NET, then you should understand what I was trying to achieve. I explained COMET and how to get the best scalable performance from ASP.NET; however, I think the previous article was a little too close to the wire. It demonstrated the technique well enough, but did not really contain any useful code. So, I thought I would write an API that wrapped up the functionality of the previous article into a neat set of classes that can be included in a typical web project, giving you the opportunity to leverage (and test) the idea. I'm not going to go into much detail about the threading model, because it is pretty much covered in the previous article; I'm just going to cover the API and how to use it in your web applications. I decided I would write a lightweight messaging API that is similar to the Bayeux protocol in the way it exchanges messages; however, it is not an implementation of this protocol as I believe it was overkill for what was required to get this API to work, and it is also only a draft. My original article stated I would put together a Tic-Tac-Toe game; unfortunately, I figured the idea would be easier demonstrated with a simple chat application. The application uses a COMET channel to receive messages, and a WCF service to send messages. The basic chat application
Glossary of TermsBelow is a list of terms that I use in this document, and what they are meant to describe:
The Core ProjectThe core project contains all the classes required to enable COMET in your ASP.NET application. The code is very close in design to the code in the original article, but I have extended the functionality to enable the transmission of generic messages between the clients and the server. The main class that controls the COMET mechanism is To expose your channel to the outside world, it needs to be wrapped up in an The code below demonstrates how you would setup an public class DefaultChannelHandler : IHttpAsyncHandler
{
// this is our state manager that
// will manage our connected clients
private static CometStateManager stateManager;
static DefaultChannelHandler()
{
// initialize the state manager
stateManager = new CometStateManager(
new InProcCometStateProvider());
}
#region IHttpAsyncHandler Members
public IAsyncResult BeginProcessRequest
(HttpContext context, AsyncCallback cb, object extraData)
{
return stateManager.BeginSubscribe(context, cb, extraData);
}
public void EndProcessRequest(IAsyncResult result)
{
stateManager.EndSubscribe(result);
}
#endregion
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
public static CometStateManager StateManager
{
get { return stateManager; }
}
#endregion
}
The above code is pretty simple. We have a static instance of our The rest of the implementation of the class simply maps the We also need the entry in the web.config file that enables the handler. <add verb="POST"
path="DefaultChannel.ashx"
type="Server.Channels.DefaultChannelHandler, Server" />
That's it, the channel is now ready to be subscribed to by a client. The CometClient ClassThe channel needs to keep track of clients, each client is represented in some sort of cache by an instance of the The code below shows the login action of the default.aspx file in the included chat application: protected void Login_Click(object sender, EventArgs e)
{
try
{
DefaultChannelHandler.StateManager.InitializeClient(
this.username.Text, this.username.Text, this.username.Text, 5, 5);
Response.Redirect("chat.aspx?username="
+ this.username.Text);
}
catch (CometException ce)
{
if (ce.MessageId == CometException.CometClientAlreadyExists)
{
// ok the comet client already exists, so we should really show
// an error message to the user
this.errorMessage.Text =
"User is already logged into the chat application.";
}
}
}
We are not validating a password or anything, we are simply taking the username directly from the page and using that to identify our client. A COMET client has two tokens that are supplied by the consumer of the API:
The reason why we use a public and private token is because the private token can be used to subscribe to a channel and receive messages for that user. We don't want any other client to be able to do that apart from the original client (e.g., we don't want the messages spoofed!). Because of this reason, we use the public token if we wanted to send messages between clients. I have also included a To setup a client in the channel, you need to call
In the above example, The With the Client-Side JavaScriptTo enable the client-side functionality, there is a WebResource located in the core project, Scripts/AspNetComet.js that contains all the JavaScript needed to subscribe to the channel (and a public domain JSON parser from here). To make things easier, I have included a static method on protected void Page_Load(object sender, EventArgs e)
{
CometStateManager.RegisterAspNetCometScripts(this);
}
With this call in place, we are free to use the very basic client-side API that is available to us. The example below is taken from chat.aspx in the web project, and shows how you can subscribe to a particular channel once a client has been initialized. var defaultChannel = null;
function Connect()
{
if(defaultChannel == null)
{
defaultChannel =
new AspNetComet("/DefaultChannel.ashx",
"<%=this.Request["username"] %>",
"defaultChannel");
defaultChannel.addTimeoutHandler(TimeoutHandler);
defaultChannel.addFailureHandler(FailureHandler);
defaultChannel.addSuccessHandler(SuccessHandler);
defaultChannel.subscribe();
}
}
All the functionality for the client-side API is wrapped up in a JavaScript class called
The following code shows the signatures of each handler method: function SuccessHandler(privateToken, channelAlias, message)
{
// message.n - This is the message name
// message.c - This is the message contents
}
function FailureHandler(privateToken, channelAlias, errorMessage)
{
}
function TimeoutHandler(privateToken, channelAlias)
{
}
The [DataContract(Name="cm")]
public class CometMessage
{
[DataMember(Name="mid")]
private long messageId;
[DataMember(Name="n")]
private string name;
[DataMember(Name="c")]
private object contents;
/// <summary>
/// Gets or Sets the MessageId, used to track
/// which message the Client last received
/// </summary>
public long MessageId
{
get { return this.messageId; }
set { this.messageId = value; }
}
/// <summary>
/// Gets or Sets the Content of the Message
/// </summary>
public object Contents
{
get { return this.contents; }
set { this.contents = value; }
}
/// <summary>
/// Gets or Sets the error message if this is a failure
/// </summary>
public string Name
{
get { return this.name; }
set { this.name = value; }
}
}
Sending a MessageIn the chat web application, I have included an AJAX-enabled WCF web service that acts as the end point for the "Send Message" functionality of the chat application. The code below shows the client-side event handler for the click of the Send Message button: function SendMessage()
{
var service = new ChatService();
service.SendMessage(
"<%=this.Request["username"] %>",
document.getElementById("message").value,
function()
{
document.getElementById("message").value = '';
},
function()
{
alert("Send failed");
});
}
The code constructs an instance of the The server code for [OperationContract]
public void SendMessage(string clientPrivateToken, string message)
{
ChatMessage chatMessage = new ChatMessage();
//
// get who the message is from
CometClient cometClient =
DefaultChannelHandler.StateManager.GetCometClient(clientPrivateToken);
// get the display name
chatMessage.From = cometClient.DisplayName;
chatMessage.Message = message;
DefaultChannelHandler.StateManager.SendMessage(
"ChatMessage", chatMessage);
// Add your operation implementation here
return;
}
This method looks up the function SuccessHandler(privateToken, alias, message)
{
document.getElementById("messages").innerHTML +=
message.c.f + ": " + message.c.m + "<br/>";
}
Using the CodeThe website included in the solution will execute without any configuration changes, then just connect a few clients to the application login using a desired username, and chat. Messages should be received in real-time, and appear instantly to each user. Using the API will enable you to use a COMET style approach in your AJAX enabled applications. Using WCF can be handy for sending messages to the server, this is all neatly wrapped up for you automatically, then just callback to the connected clients on a COMET channel. History
| ||||||||||||||||||||