CoverStory
LANGUAGES:
XAML | JavaScript | C#
ASP.NET
VERSIONS: 3.5
A Nice Pairing
Silverlight and ASP.NET AJAX
By Shawn Wildermuth
Creating Silverlight 1.0 applications can be much like
creating typical HTML-based pages. Very often it is about displaying
information to the user to entertain, inform, or educate. HTML and Silverlight
both do this quite well. But in today s world, the Internet browser is fast
becoming an application platform itself. This means you can create truly
interactive applications directly in the browser. Silverlight is a natural
solution for creating these experiences on the Web and in the browser. In this
same sense, these interactive applications would like to be able to communicate
with data centers and servers. Because Silverlight 1.0 applications exist on
traditional Web pages, we can use well-worn techniques for communicating with
the server. This is where ASP.NET AJAX presents a great opportunity to
repurpose this technology for use with Silverlight. In this article, I ll dive
into the why and how of using ASP.NET AJAX with your Silverlight applications
or controls.
The Problem
Creating standalone applications with Silverlight can be
very compelling. The first time you create a media player or game with
Silverlight is an exciting time. But in the majority of cases, the real power
of Silverlight applications will be in creating connected applications.
At first blush, many developers new to Silverlight find it
difficult to understand that they are writing applications meant to be running
within the client s browser. The Silverlight story is all about running
client-side code. The drawing palette and animation stack are great, but it is
forcing us as developers to look at writing code that runs in the client again.
This is a jarring experience for many ASP.NET developers. Many may have become
complacent, trying to do all the coding on the server to avoid the complexities
of the client-side development model.
Once the connected application development is being done
on the client, we must have a cohesive way to communicate with the server. We
could write Web services for the communication layer, but calling Web services
from client code is often difficult and complex. In addition, it would be nice
if we had a way of writing our client code to hide some of the cross-browser
bugs that tend to cause problems between the way different browsers have
interpreted the specifications (or to what extent they ve implemented those
standards). That s where ASP.NET AJAX can really help us integrate with
Silverlight, as well as much of the code we are going to write on the client.
Pairing Silverlight with ASP.NET AJAX
Using your Silverlight application with ASP.NET AJAX is a
natural pairing, as AJAX is all
about providing services to the client-side code. Let s start with a simple
Silverlight control that shows a star rating that allows users to select a
rating (see Figure 1).
Figure 1: A simple Silverlight ratings
control.
To convince you of the natural pairing with ASP.NET AJAX,
I ll show you two types of integration. We ll host our RatingsControl in an AJAX
page, then create a Web service that allows us to communicate bi-directionally
with the server.
Hosting Silverlight with ASP.NET AJAX
Like other Silverlight projects, we need to use the Silverlight.js
API to load our control on to our page. This is normally done with a JavaScript
function named createSilverlight, which is generated by the Silverlight 1.0
templates. In our example, I ve renamed this function as createRatingsControl,
and added a parameter for the HTML host so I can host this multiple times on a
single page (see Figure 2). Normally, the template calls this creation function
within a script block on a particular HTML (see Figure 3).
function createRatingsControl(host)
{
var scene = new
RatingsControl.Page();
Silverlight.createObjectEx({
source:
"Page.xaml",
parentElement: host,
id:
"SilverlightControl",
properties: {
width:
"150",
height:
"25",
version:
"1.0",
isWindowless:
"true",
background: "transparent"
},
events: {
onLoad:
Silverlight.createDelegate(scene, scene.handleLoad),
onError: null
});
}
Figure 2: The
createRatingsControl function.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>RatingsControl</title>
<script
type="text/javascript" src="Silverlight.js"></script>
<script
type="text/javascript" src="Page.xaml.js"></script>
</head>
<body>
<div
id="silverlightControlHost">
<script
type="text/javascript">
var host =
document.getElementById("silverlightControlHost");
createRatingsControl(host);
</script>
</div>
</body>
</html>
Figure 3: Typical creation
code in Silverlight.
In this approach, we are loading the scripts required in
the header and calling the creation function (createRatingsControl) in the body
of the div that we want to host our Silverlight control. The problem with this
approach is that for a simple HTML page, we can be virtually certain the
scripts will load in time for the function to be available once the div is
reached. As a page gets more and more complex, it becomes more difficult to be
certain about the load order of scripts. We could move the script block to the
end of the page to increase the chance that the scripts are loaded in time.
Luckily, ASP.NET AJAX comes to the rescue by providing a
couple of services that help us solve this problem. Using ASP.NET AJAX requires
that we use a server-side control called a ScriptManager. As we ll see in this
article, the ScriptManager control provides a number of services to our page.
To get started with solving our loading issue, let s first drop a new
ScriptManager on our page (see Figure 4).
<%@ Page Language="C#"
AutoEventWireup="true"
CodeBehind="Default.aspx.cs"
Inherits="RatingTester._Default" %>
<!DOCTYPE html PUBLIC "..." "...">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head
runat="server">
<title>Ratings
Control Tester</title>
</head>
<body>
<form
id="form1" runat="server">
<asp:ScriptManager
runat="server"
ID="theScriptManager">
</asp:ScriptManager>
</form>
</body>
</html>
Figure 4: Our new
ScriptManager.
By including this control, we are effectively telling the
page to be prepared to use some AJAX
functionality. Next, we need to add our scripts to our new AJAX-enabled page. To
do this, we d normally add script tags to the page (usually in the header) to
import them into our page. In ASP.NET AJAX, we ll instead allow the ScriptManager
to manage them for us. The benefit of using the ScriptManager is to ensure that
the scripts are loaded in a timely fashion, as well as ensuring that every
script is only included once. The duplication functionality in the
ScriptManager may not seem obvious, but as you use master pages and controls,
you might run into multiple places where the same script might be required by
different parts of a particular page. But using the ScriptManager, it
guarantees that all the scripts on a particular page are not repeated (which
can cause a number of different issues).
To let the ScriptManager import our scripts, we ll add a
new Script tag inside the ScriptManager, then add each of our scripts inside
that new tag (see Figure 5).
<asp:ScriptManager runat="server"
ID="theScriptManager">
<Scripts>
<asp:ScriptReference
Path="~/silverlight.js" />
<asp:ScriptReference
Path="~/Page.xaml.js" />
</Scripts>
</asp:ScriptManager>
Figure 5: Adding scripts
to the ScriptManager.
Once our scripts are imported using the ScriptManager, we
can add a new div to host our Silverlight control:
<p>This is a tester for the RatingsControl.
Please select a rating!</p>
<div id="ratingHost">
</div>
We now are ready to load our Silverlight control into the
div named ratingHost. Instead of arbitrarily loading a script somewhere on the
page, hoping it is called only after the scripts are loaded on a page, we can
use another service of ASP.NET AJAX: the pageLoad function. ASP.NET AJAX
supports a specially named function, pageLoad, that is called after the scripts
are loaded. It s like an OnLoaded event in ASP.NET. To use it, simply define it
on a particular page (see Figure 6).
<script type="text/javascript">
function pageLoad()
{
var host =
$get("ratingHost");
createRatingsControl(host);
}
</script>
Figure 6: Using the
pageLoad function.
Note that this function is case-sensitive (so make sure it
is capitalized as pageLoad). In Figure 6 you should see that we are calling the
createRatingsControl, just as we did earlier in the div s script block. The
only change is that we are using another feature of the ASP.NET AJAX framework,
the $get function. This function is a cross-browser-friendly version of
document.getElementById (or similar methods). Not only is it easier to type,
but it will work on more browsers than even Silverlight.
We now have our control hosted on an ASP.NET AJAX page and,
by using some of the services provided, we should have a more stable story than
we would ve had in traditional ASP.NET hosting of the Silverlight control. Next,
we ll delve into the communication with the server, which is at the heart of
the AJAX stack.
Communicating with the Server via AJAX
In my interaction with many different types of developers,
I run into numerous people who are actively using ASP.NET AJAX. A majority of
these developers are using it for the controls: UpdatePanel and the AJAX
Control Toolkit. While these controls are certainly laudable for their
usefulness, I think the real power of ASP.NET AJAX is the ability to create a
simple server communication API.
For our example we want to be able to communicate
bi-directionally with the server. Bi-directional communication with AJAX is
really a push-pull model. Because the HTTP protocol is connectionless, we need
to have our client code initiate all the communications. When we need to tell
the server something, we need to send it a message; whereas, if we need to get
information from the server, we need to make a server request. For developers
used to non-Web-based communication, this is a different mindset that they ll
need to get used to.
ASP.NET AJAX allows us to create a simple client-side
JavaScript API by providing two services: Proxy Generation and Marshalling.
Essentially, ASP.NET AJAX allows us to expose particular Web services to the
client-side JavaScript and create proxy class(es) for Web services to make them
simple to call. In addition, ASP.NET AJAX will convert types between the client
and the server (using JSON) so we can use complex types, as well as simple
types from JavaScript without having to do much in the way of type conversion.
These services will become clearer as we develop our Web service and use it in
code.
In our RatingsControl, we want to communicate two things:
set the ratings for a particular item and get any previously rated items. To do
this, we want to create a simple Web service that exposes two methods:
SetRating and GetRating. Let s start with the SetRating call. We can define our
traditional ASP.NET Web service like that shown in Figure 7.
/// <summary>
/// Service to get and set ratings for use with the Silverlight
Ratings Control
/// </summary>
[WebService(Namespace =
"http://adoguy.com/ratingservice")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class RatingsService : System.Web.Services.WebService
{
}
Figure 7: The Web service.
The only difference between a traditional ASP.NET .asmx
service and one that will work with ASP.NET AJAX is the addition of the
ScriptService attribute. This attribute tells ASP.NET that we want to be able
to use the service from ASP.NET AJAX; therefore, all types must be convertible
to JavaScript Object Notation (JSON). JSON is a format that is easy for
JavaScript to deal with, is compact, and is a data-only copy of structures. JSON
is not a proxy to the real object, but instead is equivalent to a copy of the
structure of the data within an object. See the Resources section at the end of
this article for more information on JSON. ASP.NET AJAX uses JSON under the
covers to convert objects across the wire.
Next, we ll want to add our SetRating method to allow us
to tell the server that someone has selected a rating. In our simple
implementation, we are allowing the client code to specify a name for the
rating, and we are simply storing it in an ASP.NET Session (instead of storing
it in a database or file). This approach is not appropriate for most cases, but
it makes our example very concise and easy to understand. Here s our SetRating
method:
[WebMethod(EnableSession = true)]
public void SetRating(string ratingName, double rating)
{
Session[string.Concat("RATINGFOR", ratingName)] = rating;
}
The EnableSession property on the WebMethod is only required
because we are using Session to store our rating. When this method is called,
we simply store the rating in Session for later use. The real magic for us
happens when we go back to our page and tell the ScriptManager that we want to
use this Web service on a page (see Figure 8).
<asp:ScriptManager runat="server"
ID="theScriptManager">
<Services>
<asp:ServiceReference
Path="~/RatingsService.asmx" />
</Services>
<Scripts>
<asp:ScriptReference
Path="~/Silverlight.js" />
<asp:ScriptReference
Path="~/Page.xaml.js" />
</Scripts>
</asp:ScriptManager>
Figure 8: Adding a
Web service to the ScriptManager.
By adding a Services section to the ScriptManager, we can
add any Web services for which we need a JavaScript proxy on the client side. In
this example, we create a ServiceReference to our RatingsService itself. Once
this is added, it allows us to use the new proxy from JavaScript. The
RatingsService itself is created as a JavaScript class in the client that we
can call directly. Here s our use of this new Proxy class in code:
// Let the server know that a rating was picked
if (RatingsService) // Is the defined?
{
var svc = new
RatingsService();
svc.SetRating("TestRating", this.currentRating);
// Fire and Forget!
}
This code first tests to see if the RatingsService is
defined as a class. By testing this we can write this code to work both in an
ASP.NET project that has it defined and a test page that doesn t have access to
the Web service. Once we are satisfied that it does exist, we can simply create
a new instance of the service using the new syntax. The service instance is
really just a proxy that knows how to call each of the methods asynchronously. In
our case, we want to call the SetRating method. Because we want to let the
server know the value, we can simply set the value to match our signature
(where we want the name of the rating, then a number that represents the actual
star rating, which is stored in a local class variable in our Silverlight
project named currentRating).
More interestingly is if we request data from the server.
To show this, we have a new method named GetRating that takes the name of the
rating from the client and returns the actual rating. The Web service
implementation is shown in Figure 9.
[WebMethod(EnableSession = true)]
public double GetRating(string ratingName)
{
object rating =
Session[string.Concat("RATINGFOR",
ratingName)];
if (rating == null)
return -1.0;
else return (double)
rating;
}
Figure 9: The GetRating
implementation.
In this code we attempt to find the rating in the Session;
if we don t find it, we return -1; otherwise, we return the actual rating. In
our client code, we want to call this new method and have access to the
returned number (see Figure 10).
// Get the Current Rating From Web Service
if (RatingsService) // Is the defined?
{
var svc = new
RatingsService();
svc.GetRating("TestRating",
Silverlight.createDelegate(this,
this.getRatingComplete));
}
Figure 10: Calling
the GetRating Web service.
Just like before, we want to be sure that the Web service
is defined, and, if so, create a new instance of the service. Unlike before,
when we call the method, we are specifying a callback function (in this case,
we are using the Silverlight.createDelegate call to allow us to call back into
a member of our implementation class). Calling GetRating will asynchronously
call the server, then call our getRatingComplete function with the result of
the Web service call once it completes. The getRatingComplete function is shown
in Figure 11.
getRatingComplete: function(rating)
{
// Set the current rating
and recalculate it
if (rating >= 0)
{
this.ratingPicked =
true;
this.setRating(rating);
}
},
Figure 11: The callback
function.
In the callback function, we are being passed the rating
(which is just the return type of our Web service). If the rating is greater than
or equal to zero, we know the service found a valid rating and we can tell the
control that a rating was picked and set the rating (to show the correct number
of stars).
Conclusion
Silverlight represents a new platform in the Microsoft Web-space.
Instead of reinventing the entire stack or forcing developers to use a
single-use set of technologies, Silverlight and ASP.NET AJAX represent an
example of using existing skill-sets in these new development models. By
leveraging existing knowledge, Silverlight can fit comfortably into existing Web
sites and developer toolboxes. Using ASP.NET AJAX s Web service proxy creation
services allows the developer to continue to focus on the functionality and
concern themselves less with the plumbing. At the end of the day that means we ll
be able to deliver better solutions in less time.
Resources
Microsoft s Silverlight Site: http://silverlight.net
The Silverlight Tour: http://silverlight-tour.com
JSON Defined: http://en.wikipedia.org/wiki/JSON
My Blog: http://adoguy.com
The source code
accompanying this article is available for download.
Shawn Wildermuth is
a Microsoft MVP (C#), MCSD.NET, MCT, and the founder of Wildermuth Consulting
Services, LLC, a company dedicated to delivering architecture, mentoring, and
software solutions in the Atlanta, GA
area. He also is a speaker on the INETA Speaker s Bureau, and has appeared at
several national conferences to speak on a variety of subjects. He currently is
teaching Silverlight across the country during his Silverlight Tour (http://silverlight-tour.com). Shawn
also is the author of several books, including Pragmatic
ADO.NET (Addison-Wesley), and co-author of four Microsoft Certification
Training Kits for MS Press, as well as the upcoming book, Prescriptive Data Architectures. He has been
writing articles for a number of years for a variety of magazines and Web sites,
including MSDN, MSDN Online, DevSource, InformIT, Windows IT Pro, The
ServerSide .NET, ONDotNet.com, and Intel s Rich Client Series. Shawn has
enjoyed building data-driven software for more than 20 years. He can be reached
at his Web site at http://www.wildermuthconsulting.com.