Wednesday, April 29, 2009

.NET: Getting HTTP Headers

As part of the Silverlight application I’m working on, I want to reduce our bandwidth requirements as well as give the users the option of having a faster experience by offering to cache the images locally on the user’s Isolated Storage. However, with Silverlight you only get 1 MB of Isolated Storage when online in-the-browser, and with Silverlight 3 you get 25 MB out-of-browser, but I need more than that. The problem is I didn’t want to download each file first to figure out how much space I needed to request from the Isolated Storage mechanism (because you have to specifically ask to increase the quota to a certain size, and then the user must accept it in a dialog).

So HTTP headers come to the rescue. Using the HEAD command, I can get something that looks like the following from each of the images I know I need to cache, without having to download the whole thing:

image

Notice the Content-Length: 26529. That’s the size, in bytes, of that file. And also notice that there’s no data returned after the headers. That means that this request will go VERY fast, and I can blow through them all very quickly. Note that this is also useful for finding the last modified date, which I will be using to determine if the locally cached file is out of date.

cURL gives a good interface for calling the HTTP HEAD verb, all you have to do is add the “--head” argument. But how do we do this in .NET?

Answer: use System.Net.HttpWebRequest!

image

Notice line 8: here we’re telling the request object to use the HEAD verb so that no data is returned. Then, we call request.GetResponse to get the response, and the ContentLength is a property on that returned WebResponse object. Pretty easy! The output is:

image

And we see that the values match, of course. Make sure you put that request.Method = “HEAD” line in there. Other examples on the web left out that line, and so it was actually doing a full request.

If you notice above, I didn’t give a type for the request and response objects. Even though I called HttpWebRequest.Create(), it returns a WebRequest object. Although that works for getting the content length, there’s a bunch more you can do with the HttpWebRequest and HttpWebResponse objects. Let’s try the caching concept I mentioned earlier.

image

Using the HTTP Header of If-Modified-Since, we can give the web server the date of our cached version, and if it hasn’t changed since then, it returns an HTTP 304 Not Modified status code. If it has changed, it returns HTTP 200 OK. The HttpWebRequest actually throws a WebException if it gets a 304 code back, so we have to catch it.

First, in the above code, we’re setting the HttpWebRequest.IfModifiedSince property to 1/1/2008. (The file was modified in 2006.) Then, if it’s been modified since 1/1/2008, we print out the response status code and description, as well as the content length and last-modified date. If it returns 304, it throws a WebException, but in the ex.Response property we can access the status code and description and other headers. So here’s what this example returns:

image

So now we know that we do not need to get the data, because it hasn’t been modified since our request date. But let’s move that request.IfModifiedSince date back to 2005 and see what happens.

image

So you can see that the file was modified in 2006, after our request date of 2005, so we should get the data.

Note that if you’re strictly doing a cache-update scenario and you don’t need to pay attention to the file size, you don’t have to just do a HEAD request. You could do a full GET and have the data returned if it has been modified, and if it hasn’t, it’ll throw a 304 Not Modified without any data.

Now, I’m not sure if the whole HttpWebRequest and HttpWebResponse API is available in Silverlight yet, but this at least gets me started.

Hope this helps!

Sunday, April 26, 2009

Silverlight: The Huge URI “Gotcha”

I am working on a pretty cool Silverlight project, and I’m excited to show you what I’ve been working on. But until then, here’s another post.

I originally started developing the project in Silverlight 2, but since Silverlight 3 was released with out-of-browser support, I decided to start using it. Well, one thing I noticed right off the bat was that my URIs weren’t resolving out-of-browser. I found out that this was because I was using absolute URIs by getting the browser’s location and parsing it out (horribly not best practice, as I’ve since found out), instead of doing a relative URI.

One thing about relative URIs in Silverlight is that they are from the site-of-origin, which is the location of the .xap file. If you create a Silverlight project with an automatically created web project, it puts the .xap file in the ClientBin folder. And since you can’t do “../” in your relative URIs, everything has to be a descendant of your ClientBin. You can remedy this by moving the .xap to the root of the web project by looking at the properties of the web project. There’ll be a Silverlight tab on the left, and you can remove the application, and re-add it, but empty out the Destination Folder box so that it will put it in the root.

Another big URI “gotcha” in Silverlight is that there are different forms of relative URIs when it comes to images. If you’re doing a standard WebClient request, you can use “MyFolder/thisdoc.xml” as a relative URI, and notice the lack of a leading slash. If you move your SL app from the ClientBin to your web app root above, this will look in the MyFolder folder of your web app and find the thisdoc.xml file.

However, if you’re passing in a URI to a BitmapImage object or other Image, it looks inside the .xap for any files flagged as build action “Resource” instead of in the web root. So if you add a leading slash for images, it will do an HTTP request to look in the web app root.

Basically, with the leading slash for images, it looks starting in the .xap folder. Without a leading slash, it looks for an included resource.

Weathered Silverlight devs probably know all this already, but I found it frustrating when trying to offline-enable my application. Hope this helps!

Friday, April 17, 2009

ASP.NET: Accessing Session State from Outside (Better)

If any of you have been following this blog for a while, you may remember my last post about accessing the ASP.NET session state from outside your web application (i.e. from a class library). Well, I have since found a much better way of doing it. Yes, my previous solution works, but I found a solution that doesn’t require passing in the session reference into the constructor. This functionality is painfully simple, and I think it has been there since ASP.NET 1.0 or 1.1.

Add Reference

First, add a reference to System.Web.dll to your class library.

image

Imports/Using

Next, you’ll need to import the System.Web and System.Web.SessionState namespaces into your class.

image

Call on HttpContext

Finally, all you have to do to access your session state is call on HttpContext.Current.Session, like this:

image

Improving The Code

Really, that’s all there is to it. However, what if the user has not had anything stuck into their session yet? Then you’ll get an Object Reference Not Set To An Instance Of An Object error, because HttpContext.Current.Session is nothing. To remedy this problem, I like to use a “dummy” session key to activate the session for every call to it.

image

While this function could possibly return Nothing, that may be your intended behavior by checking String.IsNullOrEmpty() on the return value. But either way, this allows you to access the SessionState from a class library without passing it into the constructor.

I am quite embarrassed that I didn’t realize this first, but hey, that’s the constant evolution of a programmer, right?

Update: The last above example doesn’t make sense, as a commenter pointed out. And setting a dummy value is really only beneficial in getting the SessionID. I apologize for that mistake, no I did not test that code for that condition.

Here’s a better example, I created 2 projects, one Web and one Class Library. I added System.Web as a reference to the class library project, and added the following code:

image

Then, on the Default.aspx on the web project, I added the class library as a project reference and added the following controls:

image

And on the code-behind, added the following code:

image

And now when we run it and just click Get Name first, it sets the label beside it to nothing:

image

But when we put in a name in the text box, click Set Name, then click Get Name, it works:

image

Sorry for the confusion.

Tuesday, April 7, 2009

ASP.NET: Routing with WebForms

I had lunch with a colleague of mine today and he reminded me of the importance of Search Engine Optimization with a public-facing, eCommerce website like the one I’m working on. Due to pre-existing dependencies, it was too tough to migrate to MVC from WebForms for the development of this website. Which, one benefit of MVC is the clean, SEO-friendly URLs due to the ASP.NET routing engine in .NET 3.5 SP1.

What you may know is that the routing engine can also work with WebForms, not just MVC. It does take a bit more work, though, and a lot of the blog posts on the subject are incomplete. So I’ll here try and thoroughly demonstrate how to use Routing with your WebForms app.

Scenario: You have an eCommerce site, with items grouped by categories. You’ll probably have a URL for your individual items like /Shopping/Item.aspx?id=MyItem123. The problem with this is that Google and other engines will place less of a priority on querystring parameters than on discrete URLs. So /Products/MyItem123/My-Sun-Glasses would be a better URL because it not only avoids the querystring, but also gives keywords of the title of the item in the URL itself. Note that sites like Stack Overflow use this technique, where the URL looks like /question/123456/this-is-my-question-about-asp-net. (They actually use MVC, but as far as the routing, I mean they do /question/{id}/{title}, where they ignore what's in the title field, it's merely there for SEO and friendly URLs.)

Let’s start out with a standard, WebForms site that uses querystring URLs.

image

And here’s what the pre-SEO-optimization code-behind looks like:

image

Clearly this is an overly simplified example, but it will demonstrate how to use the routing engine with WebForms. Ideally we want our URLs to look like /Products/1/This-is-item-1 in this example.

Step 1 – Add Reference

First, we need to right-click our web project and choose Add Reference… and choose System.Web.Abstractions and System.Web.Routing.

image

Step 2 – Web.Config Changes

Next, you need to modify a few lines of your Web.Config file. First, locate the httpModules section, and add the following line:

<add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

Then, you’ll need to add the following property to the system.webServer/modules tag in the Web.Config, like so:

image

Next, you’ll need to add the following line inside that system.webServer/modules block:

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

And finally, you’ll need to add the following line to the system.webServer/handlers block:

<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>

Step 3 – The IRoutablePage Interface

The next step is required so that your pages know how to access the dynamic route data that is passed in, such as the “id” in this case. You need to make every page implement the following interface, which you will add to your project:

image

Very simple, you’re just passing to the page the System.Web.Routing.RequestContext that contains the RouteData (instead of the Request.QueryString data).

Step 4 – The WebFormRouteHandler Class

Now we want to create the class that actually handles the route calls. Here’s the constructor and properties:

image

And below that, here’s the IRouteHandler function:

image

Step 5 – Make Pages IRoutablePage Enabled

In order for our Default.aspx page to be used with a route engine, we need to make a few modifications. First, we need to implement the IRoutablePage which just requires a single property.

image

And here’s the property:

image

And, if you look at the original code at the top of this post, we can’t use the Request.QueryString anymore to access the “id” value. Now, we have to use _routeContext.

image

Step 6 – Tying It All Together In The Global.Asax File

Currently, our site works but the SEO-friendly URL doesn’t work. That’s because the final step in our puzzle is adding code to the Application_Start block of our Global.asax file to tie it all together.

First, though, we must import System.Web.Routing in our Global.asax file:

image

And finally, we add the following to our Application_Start event:

image

Conclusion

And now, we test it out:

image

So now, in other parts of your site, you can craft your URLs to use that naming convention and you’re now SEO-friendly with your URLs, while using most of your existing WebForms code!

Hope this helps!