Sunday, 24 April 2011

SSRS 2008 - Enabling Forms Authentication with ActiveDirectoryMembershipProvider

SQL Server Reporting Services is a very powerful reporting tool however, by default, it uses Windows Authentication (NTLM) to authenticate users. This is fine if you're running over an intranet but if you're not then you need to do a little bit of work to enable forms authentication.

Fortunately, SSRS does support forms authentication in the form of a Security Extension. To give you some background, SSRS allows you, the developer, to extend it's functionality by the means of extensions. There are four main extension types:

  1. Data Processing Extension - This allows you to define how to access data from a specific data source type not currently supported by SSRS.
  2. Delivery Extension - Once a report has been generated it can be "delivered" to various locations, for example, it can be sent to someone via an e-mail address. This allows you to code for locations not currently supported by SSRS.
  3. Rendering Extension - Currently, you can render a SSRS report in a wide array of formats, within a PDF or HTML are just two for example. A rendering extension allows you to extend this to support even more formats that aren't supported by SSRS.
  4. Security Extension - This is the one we're interested in here. Security extensions allow you to precisely define how a user is authenticated and what permissions that user has. By default, this is set to work with Window authentication.
With the ability to write our own security extension, we can set up SSRS to use forms authentication rather than Windows. Fortunately, Microsoft provide a very good example of how to do this and that can be found here: http://msdn.microsoft.com/en-us/library/aa902691(SQL.80).aspx

This sample however, does not tell you how to use a MembershipProvider, in particular, I imagine the one most people will want to use is the ActiveDirectoryMembershipProvider which will validate the username and password provided by a user with an ActiveDirectory membership store. In my particular case, I want to be able to validate against an ActiveDirectory store but I also want to perform just a little bit more validation, so, I'm going to extend the ActiveDirectoryMembershipProvider to achieve this.

Fortunately, this is all very simple to set up. As all authentication is done via a web service, you, as a developer, can treat it as its own web application and so, with a couple of entries within the report server's web.config file, a couple of lines of code within your security extension and a class that extends the ActiveDirectoryMembershipProvider, then you're good to go. Here, I explain the changes required. 

First off, lets change the web.config file. We need to create a connection string that'll link to our ActiveDirectory store, to do this, just above the system.web tag, we need to add the following:


<connectionStrings>
    <add name="ADConnectionString" connectionString="LDAP://SERVERNAME:389" />
</connectionStrings>


Following on from the Microsoft sample, you should have changed the authentication tag to look something like this:


<authentication mode="Forms" >
<forms loginUrl="logon.aspx" name="sqlAuthCookie" timeout="60" slidingExpiration="true" path="/" />
</authentication>
<authorization> 
    <deny users="?" />
</authorization>


Under this tag, you'll need to add the following:


 <membership defaultProvider="MembershipADProvider">
    <providers>
        <add
          name="MembershipADProvider"
          type="MyNamespace.CustomADMembershipProvider, CustomADMembershipProvider"
          connectionStringName="ADConnectionString
          connectionUsername="DOMAIN\admin
          connectionPassword="Password"
          enableSearchMethods="true"
          attributeMapUsername="sAMAccountName"
          connectionProtection="None"/>
    </providers>
</membership>


Obviously, you'll need to change the connection username and password to be the admin username and password which will have the correct permissions to be able to read from the membership store.
That will register the membership provider with the web application so that we can then access it from code.

You'll notice I've changed the type to relate to our custom membership provider which we've yet to write. Lets do that now...


namespace MyNamespace 
{
    public class CustomADMembershipProvider : ActiveDirectoryMembershipProvider
    {
         public override bool ValidateUser(string username, string password)
        {
            bool isValid = base.ValidateUser(username, password);
            if (isValid)
            {
                // Extra validation, for example, may we don't want anyone with the username
                // of "BadUser" to have access.
                if(username.ToUpper() == "BADUSER"){
                    isValid = false;
                }
           }
            return isValid;
        }
    }
}



Just to give you a quick run down of what's going, we're overriding the ActiveDirectoryMembershipProvider so that we can use the base implementation to help us validate against the ActiveDirectory store that we defined within the web.config file. However, we now override the ValidateUser method so we can add in our own custom validation code. The first line of the ValidateUser method just ensures that the user is a valid ActiveDirectory user. If they are then we perform our custom validation, in this code I've given a very simple example of saying that if the username is some form of "BadUser" then we should not allow them access.

If we then compile that class and put the resulting DLL file into the bin directory of the ReportServer directory, it will now be accessible from the ReportServer.

Finally, we need to modify the security extension to use this membership provider. By this stage, I'm assuming you've atleast read over the Microsoft sample on how to enable forms authentication. If so, then you should know what I mean when I say that we need to modify the LogonUser method of the authentication extension. This method is the method that all logons will go through. It doesn't matter how you're logging on to the ReportServer, be it through a web service or through ReportsBuilder, this method will always be hit. We need to modify it so it uses our CustomADMembershipProvider. This is very simple now that we've modified the web.config file and placed our CustomADMembershipProvider DLL within the bin directory of the ReportServer, infact it's so simple, it only requires a single line of code, as shown below.


public bool LogonUser(string userName, string password, string authority)
{
    return Membership.ValidateUser(userName, password);
}


And that's it, in theory, you're good to go. Within SSRS 2008 Release 1, this worked first time, however, in release two, it didn't and I had to install an update (I installed Cumalative Update 5), doing this opened up a few more problems which I had to overcome before I could actually run reports from the Reports Manager. More about that in my next blog!

Sunday, 17 April 2011

HTML 5 - Drag and Drop


So, every developer loves looking into new things right? Well I'm no different so with HTML 5 predicted to be the hot new technology on the block, I thought it only right that I take a bit of time to look into it. So, every now and again I'll be posting information regarding what new options HTML 5 will give us and today, I'm going to start with the drag and drop specification.

At the time of writing this, I have three browsers installed on my computer, Internet Explorer 9, Firefox 4 and Google Chrome 11. Drag and drop is only currently supported by two of these, Firefox and Chrome, so if you're not using one of those, none of the demo's provided in this post will work.

So, the HTML 5 specification provides us with a seven new JavaScript events to listen for:
  • dragstart
  • drag
  • dragenter
  • dragleave
  • dragover
  • drop
  • dragend
There's also a new property for HTML elements called draggable, just by setting it to true will enable an element to be draggable, for example:



Try dragging me, you should be able to move me around, although you can't drop me anywhere.

So, now we've made an element draggable, we need to be able to drop it somewhere. Using the events above, we can do just that. But first, let me quickly describe what each event is used for.

dragstart
As with most of these events, it does exactly what it says on the tin. This event fires when you very first try attempting to drag the element to which the event is attached. Returning true will enable the drag, returning false won't.

drag
Fires while you're dragging something. Essentially the same as onmousemove but obviously, only fires while you're dragging something.

dragenter
Fires when you first drag an element over the target element to which this event is attached. Return false if the target element is a drop zone.

dragleave
Fires when your mouse leaves the elements to which this event is attached while dragging another element.

dragover
Fires as you drag an element over the target element to which this event is attached. A little oddly, you need to return false if the target element is a drop zone.

drop
Fires when the user releases the mouse button while dragging over the target element that has this event attached, effectively dropping the dragged element.

dragend
Essentially the same as the drop event as in it fires when the user has released the mouse button while dragging the element, except this event is usually placed on the element being dragged rather than the drop zone element.

Now we know what all the events are used for, we can put together a clever combination and come up with a simple demo.

I'm draggable between the two grey boxes.

So, lets look at the HTML for this...

<table border="0" cellpadding="10" cellspacing="10" style="width: 100%;">
  <tbody>
     <tr>
       <td style="text-align: center;" width="50%">
          <div id="zoneOne" ondragenter="return dragEnter(event);" ondragover="return dragOver(event);" ondrop="return dragDrop(event);" style="background-color: grey; height: 100px; padding: 5px; text-align: center; width: 100%;">
              <div id="dragObj" draggable="true" ondragend="return dragEnd(event);" ondragstart="return dragStart(event);" style="background-color: red; margin: 5px; padding: 10px; width: 50%;">
I'm draggable between the two grey boxes.
              </div>
          </div>
       </td>      
       <td width="50%">
           <div id="zoneTwo" ondragenter="return dragEnter(event);" ondragover="return dragOver(event);" ondrop="return dragDrop(event);" style="background-color: grey; height: 100px; padding: 5px; text-align: center; width: 100%;">
           </div>
       </td>   
    </tr>
  </tbody>
</table>

I've highlighted the drag and drop related mark up in red. There's nothing special here, we mark our draggable element by setting the draggable property to true on that element. Then the rest is just event mapping. We map the dragstart and dragend events to the element that we're going to be dragging around screen. We then map the dragenter, dragover and drop events on the drop area elements. So, what do those mappings do?

Here's the code for them:

  function dragEnter(ev){
    return false;
  }
  function dragDrop(ev){
    var idelt = ev.dataTransfer.getData("Text");
    var elem = document.getElementById(idelt);
    ev.target.appendChild(elem);
    ev.stopPropagation();
    return false;
  }
  function dragOver(ev){
    return false;
  }
  function dragStart(ev){
    ev.dataTransfer.effectAllowed='move';
    var id = ev.target.getAttribute('id');
    ev.dataTransfer.setData("Text", id);
    return true;
  }
  function dragEnd(ev){
    ev.dataTransfer.clearData("Text");
    return true;
  }

Now, a quick walkthrough of each function...

  • dragEnter always returns false. There's no conditions on which I don't want the draggable item to be droppable within the area defined.
  • dragOver function does the same as the dragEnter for the same reason.
  • dragStart function sets the effectAllowed property of the dataTransfer object. This defines what the drag and drop event is actually allowed to do, in this case we say we can move it. Then we set the type of data that we want to move, in this case it's just text which we'll make the ID of the element we're dragging around. 
  • drop function then grabs that id defined within dragStart, finds the element with that id and then append its to our drop zone, effectively moving it from one zone to another.
  • dragEnd function just clears out the ID we were storing so it doesn't interfere with any future drag and drop operations.

As you've seen, I've made use of the dataTransfer object. This object only exists within the event object when we're dealing with a drag and drop operation. It essentially stores information regarding the operation as it happens. So, you can set the effectAllowed property which defines what effects are allowed within this drag and drop operation. The getData and setData methods allow us to store information regarding the operation, essentially saving us having to define extra global variables that all the functions need access to. For more information on the dataTransfer object and it's members, take a look here.

Ok, well, that's it for drag and drop. I think the next HTML 5 demo I'll be looking at, which is kind of related, is the HTML 5 File API, which will effectively allow users to be able to drag files from their desktop straight on to your web application and in the process, will upload the file to your web server. I believe this is now supported by Gmail to upload file attachments and it's all very clever stuff. More on that at a later date!

Sunday, 27 March 2011

More Web-Optimization

Ok, so we've covered the basics of making the page size as small as possible.

Now on to the more obscure time saving methods!

CSS Placement
Always ensure your CSS styles, be them inline or external references, are placed inside the HEAD tag of your HTML page. If a web browser finds an element with a class that it can't find, it won't render that element until it's parsed the entire HTML page to ensure it doesn't have to re-draw the element at a later date. If you've referenced your styles inside the HEAD tag, the browser will have the required information available, if you don't, it won't.

CSS @Import 
This statement in CSS allows you to reference another stylesheet from your original stylesheet. While that is great, it unfortunately has the side effect of essentially placing a stylesheet reference at the bottom of your HTML page, which, as we've just covered, is a bad thing. Instead, just use another stylesheet reference directly within the HEAD of the HTML, or, as we'll cover in a second, combine the two stylesheets.

JavaScript Placement
Try and ensure all your JavaScript files are placed at the bottom of your HTML page. Unfortunately, scripts block parallel downloads so placing them at the end of your HTML page, after everything has been downloaded, prevents this from blocking anything useful.

Make CSS and JavaScript External
If you make CSS and JavaScript external then the web browser can cache the relevant files so, during the next page load, the browser can access the file straight from disk, rather than going off to web server to fetch it. Not only will it lower the load on your web server, it'll also save time. Loading from a local disk is a lot faster than fetching a file across the internet! Be warned though... if your website is running off of HTTPS then you can't cache files!

Reduce HTTP Requests
Every time you request a CSS file, or a JavaScript file, or an image, or just about anything that isn't within the plain HTML, then an HTTP request has to be made for the file. There's a performance overhead with this so, reducing them should speed things up. So, try combining all of your CSS files into one, then combine all of your JavaScript files into one. As for images, try using the Sprite and Image Optimization Framework produced by Microsoft. Essentially, it'll combine all of your images into one large image and then using CSS, will only display portions of that large image so it'll seem as if each image is actually a separate image to your users. Pretty fancy stuff and again, will reduce the number of HTTP requests!

Reduce DNS Lookups
If you're accessing your resources on different servers then each time you try and grab the resource from each different server then a DNS lookup needs to be performed. For those of you that don't know what that is, it's essentially the process of finding out the IP address of a given domain name (e.g. Microsoft.com -> 65.55.12.349). There's an overhead with this lookup so reducing the number will again improve performance. With this said however, a web browser can only download a certain amount of files in parallel for a given server (in IE 7, this is limited to 2 files at any one time, I think in IE8 it's been increased to 6). So, putting resources on different servers will enable the users web browser to download more files at a given time. Obviously there's a trade off here, the more servers you spread your resources over, the more you can download at any given time but the larger the DNS lookup time penalty.

Reduce 404 Errors
There's really no need to be getting any 404 error for a resource you may, or may not require. It may not even break anything but, a 404 means you've the added expense of creating an HTTP request that does absolutely nothing, and like I covered earlier, the less HTTP requests, the better.

Turn Debugging Off
This is an ASP.NET specific performance improvement. Within your web.config file, there will be something like this line:

<compilation defaultLanguage="c#" debug="true">

Make sure debug="false". When set to true, several things happen, firstly, extra dbg files are produced and run for each aspx page compiled, that will slow down your website. However, I've found that the bigger performance problem is the extra JavaScript validation that runs, especially if you're using the Microsoft AJAX framework. In one instance, just by turning debugging off, a page that was taking 18+ seconds to load, was reduced to 2.

Ok, and that's about all I can think of for the time being. Website performance optimization is a huge subject with many a web page devoted to it. Personally, I find Yahoo's research on this invaluable so if I were you, I'd check out this guide that they've produced. It covers everything above and more. Yahoo also make some pretty awesome tools for helping with this, specifically, I've used the .NET port of their compressor which is one of the best I've come across. If you've got any other tips that aren't covered here or on Yahoo's guide, feel free to let me know, I'd love to hear them!

Sunday, 13 March 2011

HTTP Compression

Ok, so in my last post I said that minimizing the amount of data sent across the wire is a sure way of speeding up performance.

Well, there's a very simple way of doing this which I haven't discussed yet and that's by enabling HTTP compression.

HTTP Compression is a completely lossless way of making your data take up less space. There's two main forms of HTTP compression - GZip and Deflate. These two forms of compression are supported by virtually all of the main browsers now days so what one you choose to use is completely up to you but from my research, GZip seems to be the more popular.

So, how do you enable HTTP compression? Well, there's two ways:
  1. You can do it within IIS (See here for instructions on how to do that: MSDN)
  2. If you don't have access to IIS then you can do it in code using our friend Response.Filter. To do this, just use the following code and place it within your Application_BeginRequest method within your global.asax class:


void Application_BeginRequest(object sender, EventArgs e)
{
    if (Request.Headers["Accept-encoding"] != null 
        && 
        Request.Headers["Accept-encoding"].Contains("gzip"))
    {
        Response.Filter = new System.IO.Compression.GZipStream(Response.Filter, System.IO.Compression.CompressionMode.Compress, true);
        Response.AppendHeader("Content-encoding", "gzip");
    }
    else if (Request.Headers["Accept-encoding"] != null 
             && 
             Request.Headers["Accept-encoding"].Contains("deflate"))
    {
        Response.Filter = new System.IO.Compression.DeflateStream(Response.Filter, System.IO.Compression.CompressionMode.Compress, true);
            Response.AppendHeader("Content-encoding", "deflate");
    }
}


So, what we're doing here is, we're checking to make sure that the web browser supports GZip compression and if so, we set up a new GZipStream which will compress our output before sending it out to the client. If the browser doesn't support GZip compression then we fall back to Deflate and check to see if the browser supports that and if so, we use that. If neither is supported then we just send the data back uncompressed.


All very simple so there's no excuse not to use it!

My next post will continue in the same web-optimizing vein, where I'll discuss other, lesser known methods of speeding up performance of web pages.

Sunday, 6 March 2011

Optimizing Website Performance

If you've built any reasonably sized website, I can all but guarantee that someone will utter the immortal words "Can this work a bit quicker?". You'll then spend days/weeks/months doing just that so, over the coming weeks I'm going to write a series of blogs to help with this. Each blog will work on a different area and today's area is page size.

From experience, I've found that this is one of the biggest factors (well, at least in terms of client performance). The smaller the page, the faster it is to download, the faster it is to render, it's just plain faster!

So, how do you go about reducing page size? Here's a few options...

UpdatePanels / AJAX / PageMethods

Well, firstly, use AJAX calls or UpdatePanels whenever possible. Both will only cause a small portion of your page to be sent to the client, rather than the whole page. This has a dramatic improvement. If you use UpdatePanels then you'll still have the overhead of the page life cycle but, if you use AJAX calls or PageMethods (which are just ajax calls) then you'll avoid this so it'll be even quicker.

Remove ViewState

If you ever view the HTML that an ASP.NET WebForms page produces, then you'll see a hidden field with the name __VIEWSTATE which will consist of a huge string. This string is how WebForms maintains state but, by passing it to the client each time, the size of the page is a lot bigger than it needs to be. So, we can do two things here, firstly, disable ViewState where ever you can. On every WebControl there's an EnableViewState property, setting that to false will disable ViewState for that control. Secondly, we can store the ViewState on the server rather than sending it to the client. Whenever I do this, I usually store the ViewState on the Session object. There's a few articles that will tell you how to do this, personally, I'd suggest reading this one before you start coding anything though: http://www.hanselman.com/blog/MovingViewStateToTheSessionObjectAndMoreWrongheadedness.aspx

Client IDs

If you're using ASP.NET 4.0 then you have the option of changing the ClientIdMode. This is a new feature and essentially lets you specify the exact ID of server controls when they're rendered on the client. In previous versions, if you had a control with an id of "example", then assuming it was the only control on the page, it'd be rendered with the id of "ctl00_example". If you then start getting nested controls you'll get the id of "ctl00_parentId_childId_example", as you can imagine, in large systems these ids can get pretty large, pretty fast. In .NET 4.0, you can set the ClientIDMode property of the page to be "static". When this is done, all the IDs will be rendered on the client with the id that was actually set on the server. So, if we had a control with an id of "example", then no matter where it was rendered, it'd always have the id of "example". No extra characters to make it unique. Obviously, you have to be a bit more careful when deciding on the IDs of your controls, you don't want any conflicts but just by changing that property, you can save yourself a significant amount of space.

Disable EventValidation

I'm a little reluctant to suggest this one but I'll mention it anyway. Essentially, on the Page object, there's a property called "EnableEventValidation". When set to true (which is the default), it'll validate any postback and callback events for invalid data. So, for example, it'll ensure that the value sent back for a DropDownList is actually present within the list and it'll ensure you're not trying to postback a value for a control that isn't visible. To help it do this, it sends data to the client in the form of a hidden field called "__EVENTVALIDATION". If you check the HTML of your page, you should be able to see it. Obviously, this takes up a few extra bytes that aren't strictly needed so, if you disable it, this hidden field disappears and your page size gets a little smaller but if you do this, I seriously suggest you re-implement the validation on the server. For more information regarding event validation, check out the MSDN article about it.

Minification

External javascript files and CSS files are also sent across the wire and can affect how responsive your site seems. To help with this, most files can benefit from being "minified". By this I mean that you'll give a tool a script, it'll strip out all the white space. It'll rename all the local variables into a one letter equivalent and essentially, it'll get rid of absolutely everything that isn't necessary, leaving you with the smallest possible file. There are plenty of tools out there that'll do this for you, for free. A quick google search revealed a few: Microsoft MinifierMinify CSS

Image Size

Images are by far and away, the biggest files that'll be requested by a client on any normal web page request and if you don't try and optimize these, it makes all the above points a little pointless. Most images are bloated, they contain a lot of information that simply isn't needed and can be removed with no loss what so ever to quality. Yahoo's Smush It! is an excellent tool for image optimization and I strongly suggest you use it. You simply give it an image, it takes it, strips out all the unnecessary stuff and returns the smaller image, with no loss to quality. It's an excellent tool and should be a bookmark on every web developers computer.

Well, that should get you started. If you follow the above then you should see a dramatic decrease in your page sizes and hopefully, an increase in performance. In my next blog I'm going to talk about implementing loss-less HTTP compression using GZip and Deflate. These will decrease your page size even further!

Monday, 28 February 2011

Response.Filter and UpdatePanels

Carrying on from my last blog, have you ever tried to use a response filter with UpdatePanels? If so, then I would imagine you've come across this error message:

"The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled."



The problem is, the JavaScript receiving the text from the server will expect it in a certain format, as discussed in a previous post. If the text it receives doesn't conform to that format, or doesn't validate correctly (say for example, the length of the text sent doesn't match the length registered for it) then you'll get the above error.


There is a solution to this though, as we now know the format of the UpdatePanel response AND we know how response filters work, we can combine our knowledge of the two to do the following:

  1. Capture all the output using Response.Filter, taking into account "chunking".
  2. Create "UpdatePanelResponse" objects which will parse the output into objects that the JavaScript on the client expects.
  3. Transform the output.
  4. Output it to the client using our "UpdatePanelResponse" object to structure the output into the format we need.
Ok, so I'm now going to go through each step, detailing the code as we go...


Step 1 - Capture all the output using Response.Filter

Using Response.Filter, we need to capture all the output but we've got to remember that Response.Filter uses chunking. Essentially, the page output will be cut up into "chunks" of around about 16kb, however, to create our UpdatePanelResponse objects, we'll need the whole output. So, we need to grab each chunk as it comes and "cache" it, when we've grabbed the entire output we can put it all back together and then use it to create our objects. There's already a very good implementation out there implemented by Rick Strahl (http://west-wind.com/weblog/posts/72596.aspx) so I'm going to use his implementation with one minor modification, I'm going to make it implement the following interface:


public interface IFilterStream
{        
    event Func<String, String> TransformString; 
}

You'll see why a bit later. The thing I like about Rick's implementation is that he exposes a few events that are useful but for this particular example, the TransformWriteString event is the most important. This event is raised when all the output has been captured then allows you to call a method which receives a string and returns a string. The returned value is then the value that's returned to the client so, we can simply grab his implementation, map a handler to the TransformWriteString that'll do our output transformation and we're good to go. Nice and simple.

Step 2 - Create "UpdatePanelResponse" objects

Ok, the plan here is to parse the output string into objects that represent the UpdatePanel response string (essentially, the diagram included in this post). Once we've created all the objects, we need a way of performing transformations on the text and then writing all of it back out again in the format that the ScriptManager on the client will understand.
So, first off, lets define the class that'll represent an UpdatePanel response string...

internal class UpdatePanelFormat
{
    internal UpdatePanelFormat()
    {
    }
    
    internal Func<String, String> TransformMethod
    {
        get;
        set;
    }
    internal string Text
    {
        get;
        set;
    }
    
    internal int Length
    {
        get
        {
            return this.Text.Length;
        }
    }
    
    internal string Info
    {
        get;
        set;
    }
    
    internal string Type
    {
        get;
        set;
    }

    private bool _hasTransformed = false;
    private void Transform()
    {
        if (!_hasTransformed)
        {
            this.Text = this.TransformMethod(this.Text);
            _hasTransformed = true;
        }
    }

    public override string ToString()
    {
        this.Transform();
        return this.Length + "|" + this.Type + "|" + this.Info + "|" + this.Text + "|";
    }
}

As you can see, this object defines the four components of an UpdatePanel response string. It also gives us a method to transform the data by defining a delegate which takes a string and also returns one. The idea being that the string coming into the method will be the original text and the string being returned will be the string that we've transformed. Finally, the ToString method returns the string in it's expected format.

So, now we have that, we need a class that will go through the entire output string and create these objects. So, here's my implementation of that. Bare in mind that the above class is defined as internal. The only other class within my implementation that is within the same assembly as that class is the one defined below.

public class UpdatePanelResponse
{
    private static UpdatePanelResponse _instance;
    public static UpdatePanelResponse Instance
    {
        get
        {
            if (_instance == null)
                _instance = new UpdatePanelResponse();
                return _instance;
        }
    }
    public Func<String, String> Transform
    {
        get;
        set;
    }
 
    public string GetTransformedText(string responseText)
    {
        List<UpdatePanelFormat> list = this.CreateIndividualFormat(responseText);
        StringBuilder sb = new StringBuilder();
        foreach (UpdatePanelFormat fmt in list)
        {
            sb.Append(fmt.ToString());
        }
        return sb.ToString();
    }
        
    private List<UpdatePanelFormat> CreateIndividualFormat(string text)
    {
        string[] components = text.Split('|');
        List<UpdatePanelFormat> callbacks = new List<UpdatePanelFormat>();
        for (int i = 0; i < components.Length - 1; i = i + 4)
        {
            UpdatePanelFormat cb = new UpdatePanelFormat();
            cb.TransformMethod = Transform;
    
            if (i + 1 < components.Length)
                cb.Type = components[i + 1];
            if (i + 2 < components.Length)
                cb.Info = components[i + 2];
            if (i + 3 < components.Length)
                cb.Text = components[i + 3];
        
            if (i + 4 < components.Length)
            {
                int j = i + 4;
                StringBuilder sb = new StringBuilder(cb.Text);
                while (true)
                {
                    if (j >= components.Length -1)
                        break;
                    
                    int t;                        
                    string v = components[j];
                    if (Int32.TryParse(v, out t))
                        break;
                    
                    sb.Append("|" + v); // Add the | that we split by.
                    j++;
                    i++;
                }
                
                cb.Text = sb.ToString();
            }
            
            callbacks.Add(cb);
        }
        return callbacks;
    }
}

Ok, so, again, we have our delegate which takes and returns a string. This is passed on to our UpdatePanelFormat class. Remember, this is our outward facing class (the other is defined as internal). Then we have our CreateIndividualFormat method. This essentially goes through the entire string, splitting by the | character which is the separator for each individual piece of information and then creating our UpdatePanelFormat objects with this information. Finally, we have our GetTransformedText method. This takes the entire output string, passes it to our CreateIndividualFormat method which will create all of our objects. It'll then go through each of these objects, appending the transformed string to a StringBuilder instance and will then, finally, return the entire transformed text.

Step 3 and 4

We now have a method of formatting our output string, now all we need to do is give our UpdatePanelResponse object a Transform method to work with and then to send it all back to the client. We do both of these things when we map everything together within the global.asax.cs. In any application I've ever dealt with, I have an UpdatePanel surround virtually everything and then other UpdatePanels within that, so, if a postback is ever made, it will always run through an UpdatePanel, with that knowledge, we can do something like this:

void Application_BeginRequest(object sender, EventArgs e)
{
    HttpApplication app = sender as HttpApplication;

    if (app.Request.FilePath.EndsWith(".aspx"))
    {
        IFilterStream s;
        if (app.Request.UrlReferrer != null &&  app.Request.UrlReferrer.AbsolutePath == app.Request.Url.AbsolutePath)
        {
            s = new ResponsePostbackStream(app.Response.Filter);
            s.TransformString += new Func<string, string>(p_TransformString);
        }
        else
        {
    s = new FilterStream(app.Response.Filter);
            s.TransformString += new Func<string, string>(s_TransformString);          
        }

        app.Response.Filter = (System.IO.Stream)s;
    }
}

string p_TransformString(string arg)
{
    UpdatePanelResponse.Instance.Transform = s_TransformString;
    return UpdatePanelResponse.Instance.GetTransformedText(arg);
}

string s_TransformString(string arg)
{        
    return arg.Replace("/TestSite/", "/LiveEnv/");
}

As you can see, if we're NOT a postback, I use the same implementation as defined within http://clementscode.blogspot.com/2011/02/responsefilter-what-how-and-why.html except within the Write method, I raise the TransformString event and I also make the class implement the IFilterStream interface. This maps directly to s_TransformString which will replace all instances of /TestSite/ with /LiveEnv/. If we are a postback then we need to do a little bit more work, essentially, we map to p_TransformString which uses the UpdatePanelResponse object we've just created. It sets the Transform delegate to s_TranformWriteString so the exact same transformation that is taking place on normal requests, is also happening on postbacks. It then grabs and returns the transformed text which is output to the client.

And we're done! The JavaScript error will now longer appear.

Just bare in mind when using this that it'll obviously affect performance. How much will depend on your site, how big the pages are and what your transformations actually consist of so you should probably test it first to assess the impact. Equally, it could also have implications on your applications memory usage, the ResponseFilterStream implementation can use a fair amount of memory, as Rick describes in his blog post, again, you should monitor this and make sure it doesn't affect your application too much.

There's a fair amount of code there, some of which I may not have described very well so here's the source code zipped up.
Feel free to play around with it till your hearts content.

Sunday, 20 February 2011

Response.Filter - What? How? And Why?

Response.Filter allows you to change the HTML that is output to the client after .NET has processed everything. It essentially gives you a central place to make any last second changes.

So, what's the use of this? Well, there's actually quite a few uses. For example, say you've developed your web application for a test environment and so, all your hyperlinks look something like: http://www.example.com/Test/APage.aspx. Then, when you go to a live environment, you may want to change all of those links to http://www.example.com/Live/APage.aspx. Well, you could go through your entire application and change the links where necessary, or, you could just add a response filter and have that take care of it.

There's a few ways you can assign the filter but my personal favourite is to assign it within the Application_BeginRequest method within the global.asax file1. So, you end up with a method that looks like this:

void Application_BeginRequest(object sender, EventArgs e)
{
    HttpApplication app = sender as HttpApplication;
    if(app.Request.FilePath.EndsWith(".aspx"))
        app.Response.Filter = new Filter(app.Response.Filter);
}

We check to make sure that the FilePath is an actual .aspx page as every request dealt with by the application will pass through this method. That means every request for an image, javascript file, css file or any other file you can think of, will pass through here and the chances are, you'll only want to apply your logic when requesting aspx pages.

Now, we need to create the Filter class. The purpose of this class is to take the HTML that the application will create and modify it to what we need, then pass that back out to the client, so in this example, we want to search for "/Test/" and change it to "/Live/".

The Filter class should inherit from Stream and so, a simplistic version would be the following:

public class Filter : Stream
{
    Stream s;

    public Filter(Stream stream)
    {
        this.s = stream;
    }

    public override bool CanRead
    {
        get { return s.CanRead; }
    }

    public override bool CanSeek
    {
        get { return s.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return s.CanWrite; }
    }

    public override void Flush()
    {
        s.Flush();
    }

    public override long Length
    {
        get { return s.Length; }
    }

    public override long Position
    {
        get
        {
            return s.Position;
        }
        set
        {
            s.Position = value;
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return s.Read(buffer, offset, count);
    }

    public override long Seek(long offset, System.IO.SeekOrigin origin)
    {
        return s.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        s.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        String text = System.Text.Encoding.UTF8.GetString(buffer, offset, count);
        text = text.Replace("/Test/", "/Live/");

        byte[] buff = System.Text.Encoding.UTF8.GetBytes(text);
        s.Write(buff, 0, buff.Length);  
    }
}



We've now ensured every "Test" link will change to a "Live" link, in a single, central location. This means that if in the future it needs to change again, it's a very simple change. As you can probably guess, this feature has the potential to make your life a whole lot easier and it opens up a few doors that previously, weren't available to us, well, not without a bit of work anyway. One big use that I've seen it used for is to reduce the amount of unnecessary white space that's sent down the wire, doing such a thing will decrease the amount data sent which should speed up download time.2

Now that you know how to use the Response.Filter, there's a couple of things that you should know before you start working with that property. These things have the potential to cause a few problems that are a little difficult to get around if you don't know what's going on, so I've listed the issues below.
  1. ASP.NET uses "chunking" to output pages. By this I mean, that the Write method of the Filter class defined above, will be called for every "chunk" of data, which I think is around 16kb. So, if the HTML that ASP.NET creates is bigger than 16kb, then you can sometimes see odd behaviour. Taking the above example, what if the "/Te" happened to be the last characters of one chunk and "st/" was the beginning of the next. Our simple replace wouldn't match and you'd still see the /Test/ link. If you do have pages bigger than 16kb then you really need to capture all the output, then perform your transformations, and then output everything, rather than outputting small pieces at a time. A fantastic implementation of this can be found here: http://west-wind.com/weblog/posts/72596.aspx.
  2. Using Response.Filter can cause serious problems with UpdatePanels unless you know what you're doing. In my previous blog post I showed you the format of the response text for an UpdatePanel callback, in it, it includes a Length property which indicates the length of the text being sent across the wire. If you then modify the text so it has more/less characters than it originally did, the actual length of the text won't match up with the value that indicated the length and the application will throw an error. I'll propose a method of fixing this problem in my next blog.
And that's about it, a very straight forward example of Response.Filter and some of it's uses. Enjoy!


1 For more information about global.asax, check out this MSDN article http://msdn.microsoft.com/en-us/library/2027ewzw.aspx
2 A good implementation of this can be found at http://www.codeproject.com/KB/aspnet/RemovingWhiteSpacesAspNet.aspx