Sunday, December 16, 2007

Building a Community Site - It's About People, Not Technology

Sometimes in the online world when approaching a project people seem to classify the issues facing a website as a technology issue. It's as if building a web site is this mysterious thing wrapped in so much technology it's just too much handle. Just sitting in the office of a technology provider gets nerve wracking; hearing all the techno geeks spitting out acronym after acronym and having no idea what any of it means - html, Ajax, XML, HTTP, Java, ASP, .NET, Web services, Web 2.0, what all does it mean?!

Sometimes in the online world when approaching a project people seem to classify the issues facing a website as a technology issue. It's as if building a web site is this mysterious thing wrapped in so much technology it's just too much handle. Just sitting in the office of a technology provider gets nerve wracking; hearing all the techno geeks spitting out acronym after acronym and having no idea what any of it means - html, Ajax, XML, HTTP, Java, ASP, .NET, Web services, Web 2.0, what all does it mean?!

I'm here to tell you not to worry about all those acronyms, phrases, and geeks telling you what to do, do what you know. Websites are nothing but a different way of doing your business; bricks and mortar are the traditional way, but they both have something in common and that's people. In this article I'm going to discuss community sites since they're a growing influence on the web and generating plenty of interest.

Community; what do you think of when you hear community? Think of community as your own home town. Anyone who's played Sim City should have some concept of what it takes to build a community. If you remember from the game, you had some cash to build houses, roads, and services. Now let's look at that on the web, you have some money to build a site, network connection to the Internet, and some functionality to the site. The site is your community and to build it, you need to provide some place for people to live.

In the beginning in order to grow your community you have to provide a place for your users to live. Granted a site that is mostly static is nice to visit, but I wouldn't want to live there (sound familiar?). You have to provide something on your site to encourage people to want to live there. There are the usual features like forums, weblogs, profile pages and the like, that's a good start, it gives a person a place to live.

When you add these "living spaces" for your users, consider what they provide, at a minimum they should provide your user community with the ability to express themselves. As in any community whether offline or online people want to express themselves and distinguish themselves from others in the crowd. They build this identity by the clothes they wear, cars they drive, and people they associate with. Online they do something similar; they create avatars, profiles to help them connect with others that have similar interests. And just like in the physical communities people go to churches, community activities, and other places to develop relationships with people that have similar interests. They'll do the same on your website, but you need to provide them with a means to do. So when shopping for interactive applications ensure that they will provide mechanisms for your users to build those relationships.

As a community grows it needs leadership and this is the same online. For example, if your site becomes popular and has forums, usually you'll see a few of your posters become pretty prominate in their activity, posting frequently. By providing them with moderator privileges or some special designation you allow leaders to step up, and provide the community with people who appear to have some connection to the site and help build community and participation. You could extend the moderator concept to enable your users to host online chats, or even participate in podcasts or weblog posts.

As time goes by your site leaders will come and go, and it will be up to you to monitor activity within the site and find leaders. This further enhances the community aspect and allows your community to be dynamic.

Another thing to foster growth and participation is to have periodic contests, contests that allow for some user contributed content is ideal. For example some sites have users submit stories, graphics like humorous graphics, basically anything that builds content for your site, but still enables your population to contribute to the site. The cost is relatively low, and if you have the traffic you may be able to work relationships with sponsors to provide the spoils to the winner.

The main idea to take from this article is community sites are not about technology, they're about people. Find out what people like and enable them to "own" your site. If you do this your site is sure to grow and prosper. Good luck!

Patrick Santry, owner of WWWCoder.com. Is an MVP, and has over 13 years in web and eBusiness strategy. His current position is a Solution Architect for the Blue Chip Consulting Group, LLC. in Cleveland, OH.

URL Rewriting in ASP.NET

Scott Mitchell
4GuysFromRolla.com


Introduction

Take a moment to look at some of the URLs on your website. Do you find URLs like http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary? Or maybe you have a bunch of Web pages that were moved from one directory or website to another, resulting in broken links for visitors who have bookmarked the old URLs. In this article we'll look at using URL rewriting to shorten those ugly URLs to meaningful, memorable ones, by replacing http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary with something like http://yoursite.com/people/sales/chuck.smith. We'll also see how URL rewriting can be used to create an intelligent 404 error.

URL rewriting is the process of intercepting an incoming Web request and redirecting the request to a different resource. When performing URL rewriting, typically the URL being requested is checked and, based on its value, the request is redirected to a different URL. For example, in the case where a website restructuring caused all of the Web pages in the /people/ directory to be moved to a /info/employees/ directory, you would want to use URL rewriting to check if a Web request was intended for a file in the /people/ directory. If the request was for a file in the /people/ directory, you'd want to automatically redirect the request to the same file, but in the /info/employees/ directory instead.

With classic ASP, the only way to utilize URL rewriting was to write an ISAPI filter or to buy a third-party product that offered URL rewriting capabilities. With Microsoft® ASP.NET, however, you can easily create your own URL rewriting software in a number of ways. In this article we'll examine the techniques available to ASP.NET developers for implementing URL rewriting, and then turn to some real-world uses of URL rewriting. Before we delve into the technological specifics of URL rewriting, let's first take a look at some everyday scenarios where URL rewriting can be employed.

Common Uses of URL Rewriting

Creating data-driven ASP.NET websites often results in a single Web page that displays a subset of the database's data based on querystring parameters. For example, in designing an e-commerce site, one of your tasks would be to allow users to browse through the products for sale. To facilitate this, you might create a page called displayCategory.aspx that would display the products for a given category. The category's products to view would be specified by a querystring parameter. That is, if the user wanted to browse the Widgets for sale, and all Widgets had a had a CategoryID of 5, the user would visit: http://yousite.com/displayCategory.aspx?CategoryID=5.

There are two downsides to creating a website with such URLs. First, from the end user's perspective, the URL http://yousite.com/displayCategory.aspx?CategoryID=5 is a mess. Usability expert Jakob Neilsen recommends that URLs be chosen so that they:

  • Are short.
  • Are easy to type.
  • Visualize the site structure.
  • "Hackable," allowing the user to navigate through the site by hacking off parts of the URL.

I would add to that list that URLs should also be easy to remember. The URL http://yousite.com/displayCategory.aspx?CategoryID=5 meets none of Neilsen's criteria, nor is it easy to remember. Asking users to type in querystring values makes a URL hard to type and makes the URL "hackable" only by experienced Web developers who have an understanding of the purpose of querystring parameters and their name/value pair structure.

A better approach is to allow for a sensible, memorable URL, such as http://yoursite.com/products/Widgets. By just looking at the URL you can infer what will be displayed—information about Widgets. The URL is easy to remember and share, too. I can tell my colleague, "Check out yoursite.com/products/Widgets," and she'll likely be able to bring up the page without needing to ask me again what the URL was. (Try doing that with, say, an Amazon.com page!) The URL also appears, and should behave, "hackable." That is, if the user hacks of the end of the URL, and types in http://yoursite.com/products, they should see a listing of all products, or at least a listing of all categories of products they can view.

Note For a prime example of a "hackable" URL, consider the URLs generated by many blog engines. To view the posts for January 28, 2004, one visits a URL like http://someblog.com/2004/01/28. If the URL is hacked down to http://someblog.com/2004/01, the user will see all posts for January 2004. Cutting it down further to http://someblog.com/2004 will display all posts for the year 2004.

In addition to simplifying URLs, URL rewriting is also often used to handle website restructuring that would otherwise result in numerous broken links and outdated bookmarks.

What Happens When a Request Reaches IIS

Before we examine exactly how to implement URL rewriting, it's important that we have an understanding of how incoming requests are handled by Microsoft® Internet Information Services (IIS). When a request arrives at an IIS Web server, IIS examines the requested file's extension to determine how handle the request. Requests can be handled natively by IIS—as are HTML pages, images, and other static content—or IIS can route the request to an ISAPI extension. (An ISAPI extension is an unmanaged, compiled class that handles an incoming Web request. Its task is to generate the content for the requested resource.)

For example, if a request comes in for a Web page named Info.asp, IIS will route the message to the asp.dll ISAPI extension. This ISAPI extension will then load the requested ASP page, execute it, and return its rendered HTML to IIS, which will then send it back to the requesting client. For ASP.NET pages, IIS routes the message to the aspnet_isapi.dll ISAPI extension. The aspnet_isapi.dll ISAPI extension then hands off processing to the managed ASP.NET worker process, which processes the request, returning the ASP.NET Web page's rendered HTML.

You can customize IIS to specify what extensions are mapped to what ISAPI extensions. Figure 1 shows the Application Configuration dialog box from the Internet Information Services Administrative Tool. Note that the ASP.NET-related extensions—.aspx, .ascx, .config, .asmx, .rem, .cs, .vb, and others—are all mapped to the aspnet_isapi.dll ISAPI extension.

Figure 1. Configured mappings for file extensions

A thorough discussion of how IIS manages incoming requests is a bit beyond the scope of this article. A great, in-depth discussion, though, can be found in Michele Leroux Bustamante's article Inside IIS and ASP.NET. It's important to understand that the ASP.NET engine gets its hands only on incoming Web requests whose extensions are explicitly mapped to the aspnet_isapi.dll in IIS.

Examining Requests with ISAPI Filters

In addition to mapping the incoming Web request's file extension to the appropriate ISAPI extension, IIS also performs a number of other tasks. For example, IIS attempts to authenticate the user making the request and determine if the authenticated user has authorization to access the requested file. During the lifetime of handling a request, IIS passes through several states. At each state, IIS raises an event that can be programmatically handled using ISAPI filters.

Like ISAPI extensions, ISAPI filters are blocks of unmanaged code installed on the Web server. ISAPI extensions are designed to generate the response for a request to a particular file type. ISAPI filters, on the other hand, contain code to respond to events raised by IIS. ISAPI filters can intercept and even modify the incoming and outgoing data. ISAPI filters have numerous applications, including:

  • Authentication and authorization.
  • Logging and monitoring.
  • HTTP compression.
  • URL rewriting.

While ISAPI filters can be used to perform URL rewriting, this article examines implementing URL rewriting using ASP.NET. However, we will discuss the tradeoffs between implementing URL rewriting as an ISAPI filter versus using techniques available in ASP.NET.

What Happens When a Request Enters the ASP.NET Engine

Prior to ASP.NET, URL rewriting on IIS Web servers needed to be implemented using an ISAPI filter. URL rewriting is possible with ASP.NET because the ASP.NET engine is strikingly similar to IIS. The similarities arise because the ASP.NET engine:

  1. Raises events as it processes a request.
  2. Allows an arbitrary number of HTTP modules handle the events that are raised, akin to IIS's ISAPI filters.
  3. Delegates rendering the requested resource to an HTTP handler, which is akin to IIS's ISAPI extensions.

Like IIS, during the lifetime of a request the ASP.NET engine fires events signaling its change from one state of processing to another. The BeginRequest event, for example, is fired when the ASP.NET engine first responds to a request. The AuthenticateRequest event fires next, which occurs when the identity of the user has been established. (There are numerous other events—AuthorizeRequest, ResolveRequestCache, and EndRequest, among others. These events are events of the System.Web.HttpApplication class; for more information consult the HttpApplication Class Overview technical documentation.)

As we discussed in the previous section, ISAPI filters can be created to respond to the events raised by IIS. In a similar vein, ASP.NET provides HTTP modules that can respond to the events raised by the ASP.NET engine. An ASP.NET Web application can be configured to have multiple HTTP modules. For each request processed by the ASP.NET engine, each configured HTTP module is initialized and allowed to wire up event handlers to the events raised during the processing of the request. Realize that there are a number of built-in HTTP modules utilized on each an every request. One of the built-in HTTP modules is the FormsAuthenticationModule, which first checks to see if forms authentication is being used and, if so, whether the user is authenticated or not. If not, the user is automatically redirected to the specified logon page.

Recall that with IIS, an incoming request is eventually directed to an ISAPI extension, whose job it is to return the data for the particular request. For example, when a request for a classic ASP Web page arrives, IIS hands off the request to the asp.dll ISAPI extension, whose task it is to return the HTML markup for the requested ASP page. The ASP.NET engine utilizes a similar approach. After initializing the HTTP modules, the ASP.NET engine's next task is to determine what HTTP handler should process the request.

All requests that pass through the ASP.NET engine eventually arrive at an HTTP handler or an HTTP handler factory (an HTTP handler factory simply returns an instance of an HTTP handler that is then used to process the request). The final HTTP handler renders the requested resource, returning the response. This response is sent back to IIS, which then returns it to the user that made the request.

ASP.NET includes a number of built-in HTTP handlers. The PageHandlerFactory, for example, is used to render ASP.NET Web pages. The WebServiceHandlerFactory is used to render the response SOAP envelopes for ASP.NET Web services. The TraceHandler renders the HTML markup for requests to trace.axd.

Figure 2 illustrates how a request for an ASP.NET resource is handled. First, IIS receives the request and dispatches it to aspnet_isapi.dll. Next, the ASP.NET engine initializes the configured HTTP modules. Finally, the proper HTTP handler is invoked and the requested resource is rendered, returning the generated markup back to IIS and back to the requesting client.

Figure 2. Request processing by IIS and ASP.NET

Creating and Registering Custom HTTP Modules and HTTP Handlers

Creating custom HTTP modules and HTTP handlers are relatively simple tasks, which involve created a managed class that implements the correct interface. HTTP modules must implement the System.Web.IHttpModule interface, while HTTP handlers and HTTP handler factories must implement the System.Web.IHttpHandler interface and System.Web.IHttpHandlerFactory interface, respectively. The specifics of creating HTTP handlers and HTTP modules is beyond the scope of this article. For a good background, read Mansoor Ahmed Siddiqui's article, HTTP Handlers and HTTP Modules in ASP.NET.

Once a custom HTTP module or HTTP handler has been created, it must be registered with the Web application. Registering HTTP modules and HTTP handlers for an entire Web server requires only a simple addition to the machine.config file; registering an HTTP module or HTTP handler for a specific Web application involves adding a few lines of XML to the application's Web.config file.

Specifically, to add an HTTP module to a Web application, add the following lines in the Web.config's configuration/system.web section:




The type value provides the assembly and class name of the HTTP module, whereas the name value provides a friendly name by which the HTTP module can be referred to in the Global.asax file.

HTTP handlers and HTTP handler factories are configured by the tag in the Web.config's configuration/system.web section, like so:




Recall that for each incoming request, the ASP.NET engine determines what HTTP handler should be used to render the request. This decision is made based on the incoming requests verb and path. The verb specifies what type of HTTP request was made—GET or POST—whereas the path specifies the location and filename of the file requested. So, if we wanted to have an HTTP handler handle all requests—either GET or POST—for files with the .scott extension, we'd add the following to the Web.config file:




where type was the type of our HTTP handler.

Note When registering HTTP handlers, it is important to ensure that the extensions used by the HTTP handler are mapped in IIS to the ASP.NET engine. That is, in our .scott example, if the .scott extension is not mapped in IIS to the aspnet_isapi.dll ISAPI extension, a request for the file foo.scott will result in IIS attempting to return the contents of the file foo.scott. In order for the HTTP handler to process this request, the .scott extension must be mapped to the ASP.NET engine. The ASP.NET engine, then, will route the request correctly to the appropriate HTTP handler.

For more information on registering HTTP modules and HTTP handlers, be sure to consult the element documentation along with the element documentation.

Implementing URL Rewriting

URL rewriting can be implemented either with ISAPI filters at the IIS Web server level, or with either HTTP modules or HTTP handlers at the ASP.NET level. This article focuses on implementing URL rewriting with ASP.NET, so we won't be delving into the specifics of implementing URL rewriting with ISAPI filters. There are, however, numerous third-party ISAPI filters available for URL rewriting, such as:

Implementing URL rewriting at the ASP.NET level is possible through the System.Web.HttpContext class's RewritePath() method. The HttpContext class contains HTTP-specific information about a specific HTTP request. With each request received by the ASP.NET engine, an HttpContext instance is created for that request. This class has properties like: Request and Response, which provide access to the incoming request and outgoing response; Application and Session, which provide access to application and session variables; User, which provides information about the authenticated user; and other related properties.

With the Microsoft® .NET Framework Version 1.0, the RewritePath() method accepts a single string, the new path to use. Internally, the HttpContext class's RewritePath(string) method updates the Request object's Path and QueryString properties. In addition to RewritePath(string), the .NET Framework Version 1.1 includes another form of the RewritePath() method, one that accepts three string input parameters. This alternate overloaded form not only sets the Request object's Path and QueryString properties, but also sets internal member variables that are used to compute the Request object's values for its PhysicalPath, PathInfo, and FilePath properties.

To implement URL rewriting in ASP.NET, then, we need to create an HTTP module or HTTP handler that:

  1. Checks the requested path to determine if the URL needs to be rewritten.
  2. Rewrites the path, if needed, by calling the RewritePath() method.

For example, imagine that our website had information each employee, accessible through /info/employee.aspx?empID=employeeID. To make the URLs more "hackable," we might decide to have employee pages accessible by: /people/EmployeeName.aspx. Here is a case where we'd want to use URL rewriting. That is, when the page /people/ScottMitchell.aspx was requested, we'd want to rewrite the URL so that the page /info/employee.aspx?empID=1001 was used instead.

URL Rewriting with HTTP Modules

When performing URL rewriting at the ASP.NET level you can use either an HTTP module or an HTTP handler to perform the rewriting. When using an HTTP module, you must decide at what point during the request's lifecycle to check to see if the URL needs to be rewritten. At first glance, this may seem to be an arbitrary choice, but the decision can impact your application in both significant and subtle ways. The choice of where to perform the rewrite matters because the built-in ASP.NET HTTP modules use the Request object's properties to perform their duties. (Recall that rewriting the path alters the Request object's property values.) These germane built-in HTTP modules and the events they tie into are listed below:

HTTP Module Event Description
FormsAuthenticationModule AuthenticateRequest Determines if the user is authenticated using forms authentication. If not, the user is automatically redirected to the specified logon page.
FileAuthorizationMoudle AuthorizeRequest When using Windows authentication, this HTTP module checks to ensure that the Microsoft® Windows® account has adequate rights for the resource requested.
UrlAuthorizationModule AuthorizeRequest Checks to make sure the requestor can access the specified URL. URL authorization is specified through the and elements in the Web.config file.

Recall that the BeginRequest event fires before AuthenticateRequest, which fires before AuthorizeRequest.

One safe place that URL rewriting can be performed is in the BeginRequest event. That means that if the URL needs to be rewritten, it will have done so by the time any of the built-in HTTP modules run. The downside to this approach arises when using forms authentication. If you've used forms authentication before, you know that when the user visits a restricted resource, they are automatically redirected to a specified login page. After successfully logging in, the user is sent back to the page they attempted to access in the first place.

If URL rewriting is performed in the BeginRequest or AuthenticateRequest events, the login page will, when submitted, redirect the user to the rewritten page. That is, imagine that a user types into their browser window, /people/ScottMitchell.aspx, which is rewritten to /info/employee.aspx?empID=1001. If the Web application is configured to use forms authentication, when the user first visits /people/ScottMitchell.aspx, first the URL will be rewritten to /info/employee.aspx?empID=1001; next, the FormsAuthenticationModule will run, redirecting the user to the login page, if needed. The URL the user will be sent to upon successfully logging in, however, will be /info/employee.aspx?empID=1001, since that was the URL of the request when the FormsAuthenticationModule ran.

Similarly, when performing rewriting in the BeginRequest or AuthenticateRequest events, the UrlAuthorizationModule sees the rewritten URL. That means that if you use elements in your Web.config file to specify authorization for specific URLs, you will have to refer to the rewritten URL.

To fix these subtleties, you might decide to perform the URL rewriting in the AuthorizeRequest event. While this approach fixes the URL authorization and forms authentication anomalies, it introduces a new wrinkle: file authorization no longer works. When using Windows authentication, the FileAuthorizationModule checks to make sure that the authenticated user has the appropriate access rights to access the specific ASP.NET page.

Imagine if a set of users does not have Windows-level file access to C:\Inetput\wwwroot\info\employee.aspx; if such users attempt to visit /info/employee.aspx?empID=1001, then they will get an authorization error. However, if we move the URL rewriting to the AuthenticateRequest event, when the FileAuthorizationModule checks the security settings, it still thinks the file being requested is /people/ScottMitchell.aspx, since the URL has yet to be rewritten. Therefore, the file authorization check will pass, allowing this user to view the content of the rewritten URL, /info/employee.aspx?empID=1001.

So, when should URL rewriting be performed in an HTTP module? It depends on what type of authentication you're employing. If you're not using any authentication, then it doesn't matter if URL rewriting happens in BeginRequest, AuthenticateRequest, or AuthorizeRequest. If you are using forms authentication and are not using Windows authentication, place the URL rewriting in the AuthorizeRequest event handler. Finally, if you are using Windows authentication, schedule the URL rewriting during the BeginRequest or AuthenticateRequest events.

URL Rewriting in HTTP Handlers

URL rewriting can also be performed by an HTTP handler or HTTP handler factory. Recall that an HTTP handler is a class responsible for generating the content for a specific type of request; an HTTP handler factory is a class responsible for returning an instance of an HTTP handler that can generate the content for a specific type of request.

In this article we'll look at creating a URL rewriting HTTP handler factory for ASP.NET Web pages. HTTP handler factories must implement the IHttpHandlerFactory interface, which includes a GetHandler() method. After initializing the appropriate HTTP modules, the ASP.NET engine determines what HTTP handler or HTTP handler factory to invoke for the given request. If an HTTP handler factory is to be invoked, the ASP.NET engine calls that HTTP handler factory's GetHandler() method passing in the HttpContext for the Web request, along with some other information. The HTTP handler factory, then, must return an object that implements IHttpHandler that can handle the request.

To perform URL rewriting through an HTTP handler, we can create an HTTP handler factory whose GetHandler() method checks the requested path to determine if it needs to be rewritten. If it does, it can call the passed-in HttpContext object's RewritePath() method, as discussed earlier. Finally, the HTTP handler factory can return the HTTP handler returned by the System.Web.UI.PageParser class's GetCompiledPageInstance() method. (This is the same technique by which the built-in ASP.NET Web page HTTP handler factory, PageHandlerFactory, works.)

Since all of the HTTP modules will have been initialized prior to the custom HTTP handler factory being instantiated, using an HTTP handler factory presents the same challenges when placing the URL rewriting in the latter stages of the events—namely, file authorization will not work. So, if you rely on Windows authentication and file authorization, you will want to use the HTTP module approach for URL rewriting.

Over the next section we'll look at building a reusable URL rewriting engine. Following our examination of the URL rewriting engine—which is available in this article's code download—we'll spend the remaining two sections examining real-world uses of URL rewriting. First we'll look at how to use the URL rewriting engine and look at a simple URL rewriting example. Following that, we'll utilize the power of the rewriting engine's regular expression capabilities to provide truly "hackable" URLs.

Building a URL Rewriting Engine

To help illustrate how to implement URL rewriting in an ASP.NET Web application, I created a URL rewriting engine. This rewriting engine provides the following functionality:

  • The ASP.NET page developer utilizing the URL rewriting engine can specify the rewriting rules in the Web.config file.
  • The rewriting rules can use regular expressions to allow for powerful rewriting rules.
  • URL rewriting can be easily configured to use an HTTP module or an HTTP handler.

In this article we will examine URL rewriting with just the HTTP module. To see how HTTP handlers can be used to perform URL rewriting, consult the code available for download with this article.

Specifying Configuration Information for the URL Rewriting Engine

Let's examine the structure of the rewrite rules in the Web.config file. First, you'll need to indicate in the Web.config file if you want perform URL rewriting with the HTTP module or the HTTP handler. In the download, the Web.config file contains two entries that have been commented out:




Comment out the entry to use the HTTP module for rewriting; comment out the entry instead to use the HTTP handler for rewriting.

In addition to specifying whether the HTTP module or HTTP handler is used for rewriting, the Web.config file contains the rewriting rules. A rewriting rule is composed of two strings: the pattern to look for in the requested URL, and the string to replace the pattern with, if found. This information is expressed in the Web.config file using the following syntax:




pattern to look for
string to replace pattern with


pattern to look for
string to replace pattern with

...


Each rewrite rule is expressed by a element. The pattern to search for is specified by the element, while the string to replace the found pattern with is entered in the element. These rewrite rules are evaluated from top to bottom. If a match is found, the URL is rewritten and the search through the rewriting rules terminates.

When specifying patterns in the element, realize that regular expressions are used to perform the matching and string replacement. (In a bit we'll look at a real-world example that illustrates how to search for a pattern using regular expressions.) Since the pattern is a regular expression, be sure to escape any characters that are reserved characters in regular expressions. (Some of the regular expression reserved characters include: ., ?, ^, $, and others. These can be escaped by being preceded with a backslash, like \. to match a literal period.)

URL Rewriting with an HTTP Module

Creating an HTTP module is as simple as creating a class that implements the IHttpModule interface. The IHttpModule interface defines two methods:

  • Init(HttpApplication). This method fires when the HTTP module is initialized. In this method you'll wire up event handlers to the appropriate HttpApplication events.
  • Dispose(). This method is invoked when the request has completed and been sent back to IIS. Any final cleanup should be performed here.

To facilitate creating an HTTP module for URL rewriting, I started by creating an abstract base class, BaseModuleRewriter. This class implements IHttpModule. In the Init() event, it wires up the HttpApplication's AuthorizeRequest event to the BaseModuleRewriter_AuthorizeRequest method. The BaseModuleRewriter_AuthorizeRequest method calls the class's Rewrite() method passing in the requested Path along with the HttpApplication object that was passed into the Init() method. The Rewrite() method is abstract, meaning that in the BaseModuleRewriter class, the Rewrite() method has no method body; rather, the class being derived from BaseModuleRewriter must override this method and provide a method body.

With this base class in place, all we have to do now is to create a class derived from BaseModuleRewriter that overrides Rewrite() and performs the URL rewriting logic there. The code for BaseModuleRewriter is shown below.

public abstract class BaseModuleRewriter : IHttpModule
{
public virtual void Init(HttpApplication app)
{
// WARNING! This does not work with Windows authentication!
// If you are using Windows authentication,
// change to app.BeginRequest
app.AuthorizeRequest += new
EventHandler(this.BaseModuleRewriter_AuthorizeRequest);
}

public virtual void Dispose() {}

protected virtual void BaseModuleRewriter_AuthorizeRequest(
object sender, EventArgs e)
{
HttpApplication app = (HttpApplication) sender;
Rewrite(app.Request.Path, app);
}

protected abstract void Rewrite(string requestedPath,
HttpApplication app);
}

Notice that the BaseModuleRewriter class performs URL rewriting in the AuthorizeRequest event. Recall that if you use Windows authentication with file authorization, you will need to change this so that URL rewriting is performed in either the BeginRequest or AuthenticateRequest events.

The ModuleRewriter class extends the BaseModuleRewriter class and is responsible for performing the actual URL rewriting. ModuleRewriter contains a single overridden method—Rewrite()—which is shown below:

protected override void Rewrite(string requestedPath,
System.Web.HttpApplication app)
{
// get the configuration rules
RewriterRuleCollection rules =
RewriterConfiguration.GetConfig().Rules;

// iterate through each rule...
for(int i = 0; i < rules.Count; i++)
{
// get the pattern to look for, and
// Resolve the Url (convert ~ into the appropriate directory)
string lookFor = "^" +
RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath,
rules[i].LookFor) + "$";

// Create a regex (note that IgnoreCase is set...)
Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);

// See if a match is found
if (re.IsMatch(requestedPath))
{
// match found - do any replacement needed
string sendToUrl =
RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath,
re.Replace(requestedPath, rules[i].SendTo));

// Rewrite the URL
RewriterUtils.RewriteUrl(app.Context, sendToUrl);
break; // exit the for loop
}
}
}

The Rewrite() method starts with getting the set of rewriting rules from the Web.config file. It then iterates through the rewrite rules one at a time, and for each rule, it grabs its LookFor property and uses a regular expression to determine if a match is found in the requested URL.

If a match is found, a regular expression replace is performed on the requested path with the value of the SendTo property. This replaced URL is then passed into the RewriterUtils.RewriteUrl() method. RewriterUtils is a helper class that provides a couple of static methods used by both the URL rewriting HTTP module and HTTP handler. The RewriterUrl() method simply calls the HttpContext object's RewriteUrl() method.

Note You may have noticed that when performing the regular expression match and replacement, a call to RewriterUtils.ResolveUrl() is made. This helper method simply replaces any instances of ~ in the string with the value of the application's path.

The entire code for the URL rewriting engine is available for download with this article. We've examined the most germane pieces, but there are other components as well, such as classes for deserializing the XML-formatted rewriting rules in the Web.config file into an object, as well as the HTTP handler factory for URL rewriting. The remaining three sections of this article examine real-world uses of URL rewriting.

Performing Simple URL Rewriting with the URL Rewriting Engine

To demonstrate the URL rewriting engine in action, let's build an ASP.NET Web application that utilizes simple URL rewriting. Imagine that we work for a company that sells assorted products online. These products are broken down into the following categories:

Category ID Category Name
1 Beverages
2 Condiments
3 Confections
4 Dairy Products
... ...

Assume we already have created an ASP.NET Web page called ListProductsByCategory.aspx that accepts a Category ID value in the querystring and displays all of the products belonging to that category. So, users who wanted to view our Beverages for sale would visit ListProductsByCategory.aspx?CategoryID=1, while users who wanted to view our Dairy Products would visit ListProductsByCategory.aspx?CategoryID=4. Also assume we have a page called ListCategories.aspx, which lists the categories of products for sale.

Clearly this is a case for URL rewriting, as the URLs a user is presented with do not carry any significance for the user, nor do they provide any "hackability." Rather, let's employ URL rewriting so that when a user visits /Products/Beverages.aspx, their URL will be rewritten to ListProductsByCategory.aspx?CategoryID=1. We can accomplish this with the following URL rewriting rule in the Web.config file:





~/Products/Beverages\.aspx
~/ListProductsByCategory.aspx?CategoryID=1




As you can see, this rule searches to see if the path requested by the user was /Products/Beverages.aspx. If it was, it rewrites the URL as /ListProductsByCategory.aspx?CategoryID=1.

Note Notice that the element escapes the period in Beverages.aspx. This is because the value is used in a regular expression pattern, and period is a special character in regular expressions meaning "match any character," meaning a URL of /Products/BeveragesQaspx, for example, would match. By escaping the period (using \.) we are indicating that we want to match a literal period, and not any old character.

With this rule in place, when a user visits /Products/Beverages.aspx, they will be shown the beverages for sale. Figure 3 shows a screenshot of a browser visiting /Products/Beverages.aspx. Notice that in the browser's Address bar the URL reads /Products/Beverages.aspx, but the user is actually seeing the contents of ListProductsByCategory.aspx?CategoryID=1. (In fact, there doesn't even exist a /Products/Beverages.aspx file on the Web server at all!)

Figure 3. Requesting category after rewriting URL

Similar to /Products/Beverages.aspx, we'd next add rewriting rules for the other product categories. This simply involves adding additional elements within the element in the Web.config file. Consult the Web.config file in the download for the complete set of rewriting rules for the demo.

To make the URL more "hackable," it would be nice if a user could simply hack off the Beverages.aspx from /Products/Beverages.aspx and be shown a listing of the product categories. At first glance, this may appear a trivial task—just add a rewriting rule that maps /Products/ to /ListCategories.aspx. However, there is a fine subtlety—you must first create a /Products/ directory and add an empty Default.aspx file in the /Products/ directory.

To understand why these extra steps need to be performed, recall that the URL rewriting engine is at the ASP.NET level. That is, if the ASP.NET engine is never given the opportunity to process the request, there's no way the URL rewriting engine can inspect the incoming URL. Furthermore, remember that IIS hands off incoming requests to the ASP.NET engine only if the requested file has an appropriate extension. So if a user visits /Products/, IIS doesn't see any file extension, so it checks the directory to see if there exists a file with one of the default filenames. (Default.aspx, Default.htm, Default.asp, and so on. These default filenames are defined in the Documents tab of the Web Server Properties dialog box in the IIS Administration dialog box.) Of course, if the /Products/ directory doesn't exist, IIS will return an HTTP 404 error.

So, we need to create the /Products/ directory. Additionally, we need to create a single file in this directory, Default.aspx. This way, when a user visits /Products/, IIS will inspect the directory, see that there exists a file named Default.aspx, and then hand off processing to the ASP.NET engine. Our URL rewriter, then, will get a crack at rewriting the URL.

After creating the directory and Default.aspx file, go ahead and add the following rewriting rule to the element:


~/Products/Default\.aspx
~/ListCategories.aspx

With this rule in place, when a user visits /Products/ or /Products/Default.aspx, they will see the listing of product categories, shown in Figure 4.

Figure 4. Adding "hackability" to the URL

Handling Postbacks

If the URLs you are rewriting contain a server-side Web Form and perform postbacks, when the form posts back, the underlying URL will be used. That is, if our user enters into their browser, /Products/Beverages.aspx, they will still see in their browser's Address bar, /Products/Beverages.aspx, but they will be shown the content for ListProductsByCategory.aspx?CategoryID=1. If ListProductsByCategory.aspx performs a postback, the user will be posted back to ListProductsByCategory.aspx?CategoryID=1, not /Products/Beverages.aspx. This won't break anything, but it can be disconcerting from the user's perspective to see the URL change suddenly upon clicking a button.

The reason this behavior happens is because when the Web Form is rendered, it explicitly sets its action attribute to the value of the file path in the Request object. Of course, by the time the Web Form is rendered, the URL has been rewritten from /Products/Beverages.aspx to ListProductsByCategory.aspx?CategoryID=1, meaning the Request object is reporting that the user is visiting ListProductsByCategory.aspx?CategoryID=1. This problem can be fixed by having the server-side form simply not render an action attribute. (Browsers, by default, will postback if the form doesn't contain an action attribute.)

Unfortunately, the Web Form does not allow you to explicitly specify an action attribute, nor does it allow you to set some property to disable the rendering of the action attribute. Rather, we'll have to extend the System.Web.HtmlControls.HtmlForm class ourselves, overriding the RenderAttribute() method and explicitly indicating that it not render the action attribute.

Thanks to the power of inheritance, we can gain all of the functionality of the HtmlForm class and only have to add a scant few lines of code to achieve the desired behavior. The complete code for the custom class is shown below:

namespace ActionlessForm {
public class Form : System.Web.UI.HtmlControls.HtmlForm
{
protected override void RenderAttributes(HtmlTextWriter writer)
{
writer.WriteAttribute("name", this.Name);
base.Attributes.Remove("name");

writer.WriteAttribute("method", this.Method);
base.Attributes.Remove("method");

this.Attributes.Render(writer);

base.Attributes.Remove("action");

if (base.ID != null)
writer.WriteAttribute("id", base.ClientID);
}
}
}

The code for the overridden RenderAttributes() method simply contains the exact code from the HtmlForm class's RenderAttributes() method, but without setting the action attribute. (I used Lutz Roeder's Reflector to view the source code of the HtmlForm class.)

Once you have created this class and compiled it, to use it in an ASP.NET Web application, start by adding it to the Web application's References folder. Then, to use it in place of the HtmlForm class, simply add the following to the top of your ASP.NET Web page:

<%@ Register TagPrefix="skm" Namespace="ActionlessForm"
Assembly="ActionlessForm" %>

Then, where you have

, replace that with:


and replace the closing tag with:


You can see this custom Web Form class in action in ListProductsByCategory.aspx, which is included in this article's download. Also included in the download is a Visual Studio .NET project for the action-less Web Form.

Note If the URL you are rewriting to does not perform a postback, there's no need to use this custom Web Form class.

Creating Truly "Hackable" URLs

The simple URL rewriting demonstrated in the previous section showed how easily the URL rewriting engine can be configured with new rewriting rules. The true power of the rewriting rules, though, shines when using regular expressions, as we'll see in this section.

Blogs are becoming more and more popular these days, and it seems everyone has their own blog. If you are not familiar with blogs, they are often-updated personal pages that typically serve as an online journal. Most bloggers simply write about their day-to-day happenings, others focus on blogging about a specific theme, such as movie reviews, a sports team, or a computer technology.

Depending on the author, blogs are updated anywhere from several times a day to once every week or two. Typically the blog homepage shows the most recent 10 entries, but virtually all blogging software provides an archive through which visitors can read older postings. Blogs are a great application for "hackable" URLs. Imagine while searching through the archives of a blog you found yourself at the URL /2004/02/14.aspx. Would you be terribly surprised if you found yourself reading the posts made on February 14th, 2004? Furthermore, you might want to view all posts for February 2004, in which case you might try hacking the URL to /2004/02/. To view all 2004 posts, you might try visiting /2004/.

When maintaining a blog, it would be nice to provide this level of URL "hackability" to your visitors. While many blog engines provide this functionality, let's look at how it can be accomplished using URL rewriting.

First, we need a single ASP.NET Web page that will show blog entries by day, month, or year. Assume we have such a page, ShowBlogContent.aspx, that takes in querystring parameters year, month, and day. To view the posts for February 14th, 2004, we could visit ShowBlogContent.aspx?year=2004&month=2&day=14. To view all posts for February 2004, we'd visit ShowBlogContent.aspx?year=2004&month=2. Finally, to see all posts for the year 2004, we'd navigate to ShowBlogContent.aspx?year=2004. (The code for ShowBlogContent.aspx can be found in this article's download.)

So, if a user visits /2004/02/14.aspx, we need to rewrite the URL to ShowBlogContent.aspx?year=2004&month=2&day=14. All three cases—when the URL specifies a year, month, and day; when the URL specifies just the year and month; and when the URL specifies only the yea—can be handled with three rewrite rules:





~/(\d{4})/(\d{2})/(\d{2})\.aspx
~/ShowBlogContent.aspx?year=$1&month=$2&day=$3


~/(\d{4})/(\d{2})/Default\.aspx



~/(\d{4})/Default\.aspx
~/ShowBlogContent.aspx?year=$1



These rewriting rules demonstrate the power of regular expressions. In the first rule, we look for a URL with the pattern (\d{4})/(\d{2})/(\d{2})\.aspx. In plain English, this matches a string that has four digits followed by a forward slash followed by two digits followed by a forward slash, followed by two digits followed by .aspx. The parenthesis around each digit grouping is vital—it allows us to refer to the matched characters inside those parentheses in the corresponding property. Specifically, we can refer back to the matched parenthetical groupings using $1, $2, and $3 for the first, second, and third parenthesis grouping, respectively.

Note Since the Web.config file is XML-formatted, characters like &, <, and > in the text portion of an element must be escaped. In the first rule's element, & is escaped to &. In the second rule's , an alternative technique is used—by using a element, the contents inside do not need to be escaped. Either approach is acceptable and accomplishes the same end.

Figures 5, 6, and 7 show the URL rewriting in action. The data is actually being pulled from my blog, http://ScottOnWriting.NET. In Figure 5, the posts for November 7, 2003 are shown; in Figure 6 all posts for November 2003 are shown; Figure 7 shows all posts for 2003.

Figure 5. Posts for November 7, 2003

Figure 6. All posts for November 2003

Figure 7. All posts for 2003

Note The URL rewriting engine expects a regular expression pattern in the elements. If you are unfamiliar with regular expressions, consider reading an earlier article of mine, An Introduction to Regular Expressions. Also, a great place to get your hands on commonly used regular expressions, as well as a repository for sharing your own crafted regular expressions, is RegExLib.com.

Building the Requisite Directory Structure

When a request comes in for /2004/03/19.aspx, IIS notes the .aspx extension and routes the request to the ASP.NET engine. As the request moves through the ASP.NET engine's pipeline, the URL will get rewritten to ShowBlogContent.aspx?year=2004&month=03&day=19 and the visitor will see those blog entries for March 19, 2004. But what happens when the user navigates to /2004/03/? Unless there is a directory /2004/03/, IIS will return a 404 error. Furthermore, there needs to be a Default.aspx page in this directory so that the request is handed off to the ASP.NET engine.

So with this approach, you have to manually create a directory for each year in which there are blog entries, with a Default.aspx page in the directory. Additionally, in each year directory you need to manually create twelve more directories—01, 02, …, 12—each with a Default.aspx file. (Recall that we had to do the same thing—add a /Products/ directory with a Default.aspx file—in the previous demo so that visiting /Products/ correctly displayed ListCategories.aspx.)

Clearly, adding such a directory structure can be a pain. A workaround to this problem is to have all incoming IIS requests map to the ASP.NET engine. This way, even if when visiting the URL /2004/03/, IIS will faithfully hand off the request to the ASP.NET engine even if there does not exist a /2004/03/ directory. Using this approach, however, makes the ASP.NET engine responsible for handling all types of incoming requests to the Web server, including images, CSS files, external JavaScript files, Macromedia Flash files, and so on.

A thorough discussion of handling all file types is far beyond the scope of this article. For an example of an ASP.NET Web application that uses this technique, though, look into .Text, an open-source blog engine. .Text can be configured to have all requests mapped to the ASP.NET engine. It can handle serving all file types by using a custom HTTP handler that knows how to serve up typical static file types (images, CSS files, and so on).

Conclusion

In this article we examined how to perform URL rewriting at the ASP.NET-level through the HttpContext class's RewriteUrl() method. As we saw, RewriteUrl() updates the particular HttpContext's Request property, updating what file and path is being requested. The net effect is that, from the user's perspective, they are visiting a particular URL, but actually a different URL is being requested on the Web server side.

URLs can be rewritten either in an HTTP module or an HTTP handler. In this article we examined using an HTTP module to perform the rewriting, and looked at the consequences of performing the rewriting at different stages in the pipeline.

Of course, with ASP.NET-level rewriting, the URL rewriting can only happen if the request is successfully handed off from IIS to the ASP.NET engine. This naturally occurs when the user requests a page with a .aspx extension. However, if you want the person to be able to enter a URL that might not actually exist, but would rather rewrite to an existing ASP.NET page, you have to either create mock directories and Default.aspx pages, or configure IIS so that all incoming requests are blindly routed to the ASP.NET engine.

Redirect, Transfer or Rewrite?

Earlier, we implemented a URL rewriting scheme; however, in some circumstances, you may wish to implement a Response.Redirect() or Server.Transfer() instead. One reason to do this is to forward from Default.aspx to another page, like Home.aspx. You may or may not want to do this "behind the scenes," but that is a decision for you to make yourself. As always, each option comes with its own set of pros and cons.

Redirects are essentially two GET (or POST) requests. The first request gets processed and then the Response.Redirect() sends a response back to the client with an HTTP 302 redirect command. This obviously causes a slight delay as the second request gets processed. Also, since the new URL is sent to the client, it won't be hidden. This clearly doesn't support URL beautification, which is the main reason most people implement handlers. Even though redirects won't solve your problems, they still play an important part in the overall solution and should be considered when developing your mapping solution.

Transfers, unlike redirects, keep control within the application; but, they are still treated as two requests. The difference is that instead of the client handling the HTTP 302 redirect command, the web server handles it. This means that any modules, as well as the handler, will be processed twice . There are three key things to remember when using transfers: (1) the Request object, and all of its properties and methods, will reflect the initial request (logical page) and not the physical page; (2) post-back will not work; and, (3) in order to use the transfer you have to have two handlers specified in the Web.config file. There might be a way to get the post-back to work, but I don't know what that would entail. Perhaps I will delve into the ASP.NET request process fully one day. As for the two handler issue, let me explain that in a bit more detail. As you may remember from above, you specified two handlers in the Web.config file. The reason for this is because after the Server.Transfer() is executed, ASP.NET will send the second request back through the handler. I'm not completely sure why this is a problem, but it is. So, to fix it, you need to have some way to identify what requests should be handled by ASP.NET's default handler and which should be handled by yours. I attacked this by putting all of my physical pages in a Pages directory. So, by re-adding the default handler to handle all requests to "*/Pages/*.aspx", we tell ASP.NET how to support each type of request. As I also mentioned before, this will fix the the "Error executing child request" error.

Rewrites provide the best performance because there is no back-tracking to re-handle requests. You simply change the URL and continue on with the request processing. Know that accessing the Request object will now reflect the new (physical) URL and you will not have access to the old (logical) URL. You can get around this by adding custom variables to the HttpContext, but that shouldn't be necessary for most situations.

To add support for redirects and transfers, we can simply change our Web.config file by prepending "redirect.", "transfer.", or "rewrite." to identify how we want the request handled. Then, update the IHttpHandler.ProcessRequest() method to treat them accordingly.

HttpHandler.ProcessRequest() Method

public void ProcessRequest(HttpContext context)
{
// declare vars
string requestedUrl;
string targetUrl;
int urlLength;

// save requested, target url
requestedUrl = context.Request.RawUrl;
if ( requestedUrl.IndexOf("?") >= 0 )
targetUrl = ConfigurationSettings.AppSettings[requestedUrl.Substring(0, requestedUrl.IndexOf("?"))];
else
targetUrl = ConfigurationSettings.AppSettings[requestedUrl];
if ( targetUrl == null || targetUrl.Length == 0 )
targetUrl = requestedUrl;

// handle type
if ( targetUrl.StartsWith("redirect.") )
{
context.Response.Redirect(targetUrl.Substring(9));
}
else if ( targetUrl.StartsWith("transfer.") )
{
context.Server.Transfer(targetUrl.Substring(9));
}
else
{
// if type is specified, remove it
if ( targetUrl.StartsWith("rewrite.") )
targetUrl = targetUrl.Substring(8);

// save target url length
urlLength = targetUrl.IndexOf("?");
if ( urlLength == -1 )
urlLength = targetUrl.Length;

// rewrite path
context.RewritePath(targetUrl);
IHttpHandler handler = PageParser.GetCompiledPageInstance(
targetUrl.Substring(0, urlLength), null, context );
handler.ProcessRequest(context);
}
}

Conclusion

Well, congratulations on your first HTTP handler implementation. There is plenty of room for improvement, so try to think of how you can manage the mappings to add more than just a simple URL rewriting scheme. One thing that you might want to consider is a post-back processing component. Yes, post-back is handled by ASP.NET, but performance can be increased by removing that overhead. Anyway, my point is that there are a lot of things you can do to improve this simple implementation. I encourage you to add to this and let me know how well it works out for you. I'd be interested to hear some of the things people are doing with handlers. Good luck!

Friday, December 14, 2007

Json.NET 1.1: Converting between XML and JSON

JAVASCRIPT:

1.

2.
XmlDocument doc = new XmlDocument();
3.
doc.LoadXml(@"
4.

5.

6.
Alan
7.
http://www.google.com
8.

9.

10.
Louis
11.
http://www.yahoo.com
12.

13.
");
14.

15.
string jsonText = JavaScriptConvert.SerializeXmlNode(doc);
16.
//{
17.
// "?xml": {
18.
// "@version": "1.0",
19.
// "@standalone": "no"
20.
// },
21.
// "root": {
22.
// "person": [
23.
// {
24.
// "@id": "1",
25.
// "name": "Alan",
26.
// "url": "http://www.google.com"
27.
// },
28.
// {
29.
// "@id": "2",
30.
// "name": "Louis",
31.
// "url": "http://www.yahoo.com"
32.
// }
33.
// ]
34.
// }
35.
//}
36.

37.
XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText);
38.

39.
Assert.AreEqual(doc.InnerXml, newDoc.InnerXml);
40.

Json.NET - Simplifying .NET <-> JavaScript communication

JSON (JavaScript Object Notation) is lightweight data interchange format. In recent times JSON has achieved widespread use due to the ease of which it can be parsed and then the data accessed from within JavaScript compared to alternatives like XML.

While many JavaScript libraries exist for converting JSON text to and from a JavaScript object, working with JSON in .NET has been much more problematic. Correctly escaping strings and building up objects when writing JSON text can be difficult and error prone, while parsing values back out of JSON text is harder still.

Json.NET

Json.NET is a JSON .NET API for simply and safely reading and writing valid JSON text. At the core of Json.NET, similar to the .NET XML APIs, are two classes: JsonReader and JsonWriter. Also like XML in .NET, Json.NET includes a JsonSerializer class.

Reading JSON

JsonReader is a fast, forward only, readonly cursor. Like XmlTextReader it works over the top of a TextReader and maximizes performance while minimizing memory use.

The following code is a brief example of how to read a JSON object.

string jsonText = "['JSON!',1,true,{property:'value'}]";
 
JsonReader reader = new JsonReader(new StringReader(jsonText));
 
Console.WriteLine("TokenType\t\tValueType\t\tValue");
 
while (reader.Read())
{
    Console.WriteLine(reader.TokenType + "\t\t" + WriteValue(reader.ValueType) + "\t\t" + WriteValue(reader.Value))
}

Resulting in...

TokenType ValueType Value
StartArray null null
String System.String JSON!
Integer System.Int32 1
Boolean System.Boolean True
StartObject null null
PropertyName System.String property
String System.String value
EndObject null null
EndArray null null

Writing JSON

JsonWriter is also forward only, and writes JSON text to a TextWriter. It handles formatting numbers, escaping strings and validating that the object is valid.

The following code is a brief example of how to write a JSON object.

StringWriter sw = new StringWriter();
JsonWriter writer = new JsonWriter(sw);
 
writer.WriteStartArray();
writer.WriteValue("JSON!");
writer.WriteValue(1);
writer.WriteValue(true);
writer.WriteStartObject();
writer.WritePropertyName("property");
writer.WriteValue("value");
writer.WriteEndObject();
writer.WriteEndArray();
 
writer.Flush();
 
string jsonText = sw.GetStringBuilder().ToString();
 
Console.WriteLine(jsonText);
// ['JSON!',1,true,{property:'value'}]

Which prints out: ['JSON!',1,true,{property:'value'}].

Serializing and deserializing JSON and .NET objects

JsonSerializer, similar to XML serialization, provides a hassle free way to automatically convert .NET objects to and from JSON text.

The following code is a brief example of how to serialize and deserialize JSON and .NET objects.

string jsonText = "['JSON!',1,true,{property:'value'}]";
 
JsonSerializer serializer = new JsonSerializer();
 
JavaScriptArray jsArray = (JavaScriptArray) serializer.Deserialize(new JsonReader(new StringReader(jsonText)));
 
Console.WriteLine(jsArray[0]);
// JSON!
 
JavaScriptObject jsObject = (JavaScriptObject)jsArray[3];
 
Console.WriteLine(jsObject["property"]);
// value
 
StringWriter sw = new StringWriter();
 
using (JsonWriter jsonWriter = new JsonWriter(sw))
{
    serializer.Serialize(sw, jsArray);
}
 
Console.WriteLine(sw.GetStringBuilder().ToString());
// ['JSON!',1,true,{property:'value'}]

Json.NET - Json.NET Homepage

Download Json.NET - Json.NET dll and C# source code

Json.NET

The Json.NET library makes working with JavaScript and JSON formatted data in .NET simple. Quickly read and write JSON using the JsonReader and JsonWriter or serialize your .NET objects with a single method call using the JsonSerializer.

Json.NET CodePlex Project

Json.NET Download

Features

  • Lightning fast JsonReader and JsonWriter
  • The JsonSerializer for quickly converting your .NET objects to JSON and back again
  • Json.NET can optionally produce well formatted, indented JSON for debugging or display
  • Attributes like JsonIgnore and JsonProperty can be added to a class to customize how a class is serialized
  • Ability to convert JSON to and from XML

Example

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };
 
string json = JavaScriptConvert.SerializeObject(product);
//{
//  "Name": "Apple",
//  "Expiry": new Date(1230422400000),
//  "Price": 3.99,
//  "Sizes": [
//    "Small",
//    "Medium",
//    "Large"
//  ]
//}
 
Product deserializedProduct = JavaScriptConvert.DeserializeObject<Product>(json);

History

Json.NET grew out of projects I was working on in late 2005 involving JavaScript, AJAX and .NET. At the time there were no libraries for working with JavaScript in .NET so I began to grow my own.

Starting out as a couple of static methods for escaping JavaScript strings, Json.NET evolved as features were added. To add support for reading JSON a major refactor was required and Json.NET will split into the three major classes it still uses today, JsonReader, JsonWriter and JsonSerializer.

Json.NET was first released in June 2006. Since then Json.NET has been downloaded thousands of times by developers and is used in a number of major projects open source projects such as MonoRail, Castle Project's MVC web framework, and Mono, an open source implementation of the .NET framework.

Tuesday, December 4, 2007

C# and .Net on Linux

Compile and run C# command line and GUI applications on Linux

Shekhar Govindarajan

In the past issues of PCQuest we have talked much about MS .Net and C# programming language. .Net and its programming languages (C#, VB .Net, VC++ .Net, Visual J#, Python .Net, Perl .Net, COBOL .Net, etc) are available for only the Windows platform�Win 9x, ME, NT, 2000, XP, Win CE (for portables), etc. But a project named Mono has been introduced now. This is an effort to port .Net and its languages. As of now only C# has been ported to non-Windows platforms.

Libraries, compiler/interpreter and a runtime environment are the three essentials to write and run a program on a platform (OS + hardware architecture). Libraries make the writing of large programs easy by providing commonly-used routines in the form of classes and methods (functions). The .Net framework also has a number of libraries called class libraries. The compiler and the runtime are needed to convert a program into an executable and to run this executable respectively.

Mono packages the port of .Net library classes, a C# compiler and the .Net runtime�CLR (Common Language Runtime).

A graphical Image browser applications in C# running on LinuxMono claims to run on Linux, Solaris, FreeBSD and MS Windows. And besides the Intel x86 processors (486, Pentiums), it is said to support Sparc, PowerPC and StrongArm processors too.

Without getting into more theory, let�s see how Mono works on Linux. We�ll install Mono on PCQLinux 7.1, compile some C# programs and execute them. We are assuming that you are familiar with the .Net framework and the basics of C#.

Installation
Mount this month�s PCQuest CD and change to the directory system/cdrom/unltdos/linux/mono. Install all the RPMs found in this directory as

rpm -ivh *.rpm

Among other RPMs required by Mono, the Mono RPM named mono-0.12_baselabs-20020709.i386.rpm gets installed. The basic .Net assemblies like System.Data.dll, System.dll, System.Drawing.dll, System.Web.dll and System.Xml.dll are copied to the directory /usr/lib.

Now, type in the following program in a Linux text editor and save the file as HelloMono.cs.


class HelloMono
{
public static void Main(string[ ] args)
{ System.Console.WriteLine(�Hello Mono�);
}
}

Compile the program as

mcs HelloMono.cs

Mcs is the Mono C# command-line compiler. It has many command-line options like the csc compiler of MS .Net SDK. The above command will produce an executable named HelloMono. exe. Note that this executable is not a Linux executable but a .Net executable, technically called the IL (Intermediate Language) code. To run this executable, issue the following.

mono HelloMono.exe

You will see �Hello Mono� displayed on the Linux console. For more on mcs and Mono, refer to their man pages, which can be brought up by issuing:

man mcs
man mono

GUI on Mono
Mono does not support Windows Forms for GUI widgets. However, GTK# (a C# binding of GTK+graphical library) can be used for coding GUI components in C#. You�ll find all the requisites for installing GTK# on the CD. Let�s first install it and then execute some sample GTK# programs.

Change to the directory system/cdrom/unltdos/linux/gtksharp/dep on the CD and install the RPMs as

rpm -Uvh *.rpm --nodeps

Copy the file named gtk-sharp-0.2.1.tar.gz found in the directory system/cdrom/unltdoss/ linux/gtksharp to /opt directory. Extract the archive as

tar �zxvf gtk-sharp-0.2.1.tar.gz

This will produce a directory named gtk-sharp-0.2.1 within /opt. Change to this directory and issue

./configure --prefix=/usr
make
make install

Monkey Business
Money wise with Mono: .Net development can take place on a low-cost non-Windows platform like Linux. The deployment platform can be either Linux or Windows, with the OS cost (per user, per system licenses) in Windows concerning only the end user and not the developer.

Consider the scenario of a client-server .Net application. A user will interact only with the client (which should hopefully be a pleasant-looking GUI application) and expect a familiar platform like Windows. It will not matter to him if the server part is on Windows or Linux. So, the server part can be deployed on a Linux server using Mono, saving on the cost of setting up a dedicated Windows server.

Easy migration: If Mono comes out well, Windows developers, too, can develop for Linux and other non-Windows platforms. It won�t be a surprise if later a free or low-cost .Net IDE (Integrated Development Environment) like Visual Studio .Net makes a debut for Linux.

Linux developers, who should already have a grip of C/C++, can learn C# with similar syntax and constructs as in C/C++. Or, they can wait for a Mono C++ compiler that would give them an easier migration path.

The success of all this depends upon how well Mono is developed and how close and compatible it is with the implementation of the .Net framework on Windows. Other key factors will be its performance and the look-and-feel of its GUI components.

If you�re wondering what the title Monkey Business means, here�s the mystery unraveled�Mono means monkey in Spanish.

All the assemblies related to GTK# (gtk-sharp.dll, atk-sharp.dll, gdk-sharp.dll, glib-sharp.dll and pango-sharp.dll) are copied to the directory /usr/lib. You will need to refer to them using the �r option (see below) when you compile a C# program that uses GTK# for GUI. Reboot the machine once before proceeding further.

You�ll find some GTK# sample programs (HelloWorld.cs, Menu. cs, ButtonApp.cs) in the directory /opt/gtk-sharp-0.2.1/sample. You can compile them as

mcs -r gtk-sharp -r glib-sharp HelloWorld.cs

mcs -r gtk-sharp -r glib-sharp -r System.Drawing Menu.cs

mcs -r gtk-sharp -r glib-sharp -r System.Drawing ButtonApp.cs

Note how we are referring to the various assemblies using the �r option. Next, start X Window using the startx command and from within a terminal window, change (cd) to the directory /opt/gtk-sharp-0.2.1/sample and issue the following commands to execute the respective programs.

mono HelloWorld.exe
mono Menu.exe
mono ButtonApp.exe

Since Mono is an ongoing project and not all the .Net class libraries have been ported, you may find some quirks with it. Nevertheless, it�s usable to a great extent. You can check out the port status of class libraries at www.go-mono.com/class-status.html. For more on Mono, go to www.go-mono.com. For more on GTK#, see http://gtk-sharp. sourceforge.

Net/.

Shekhar Govindarajan

About Me

Ordinary People that spend much time in the box
Powered By Blogger