Insights and discoveries
from deep in the weeds
Outsharked

Thursday, March 29, 2012

Passing control from a custom HttpHandler to the default handler in asp.net

Using System.Web.Routing and IIS7 you can do some pretty interesting stuff with a web app. It also opens up a lot of possibilities for "modernizing" old webforms applications that are stuck with ugly paths and query strings by overriding the default handlers and parsing out the page path yourself.

One thing I wanted to do was to map certain paths back to an aspx page, but dynamically - e.g. I wanted to be able to parse out a path and using complex logic, build a new "real" path+querystring that I could just pass to the default handler. This way I can create nice clean API-like paths and map them to an old, ugly query-string based API. The routing part is a piece of cake. (Well, not really, but it's not a mystery). But how do you invoke the default handler from code? There's a baffling lack of info out there, so I figured I'd post a solution for future coders.

After some digging I realized that, actually, the default handler is just the plain old System.Web.UI.Page object. I tried to just create an instance of one and call ProcessRequest like you would any other handler. Nothing at all. No error, no output.

Microsoft is decidedly no help with this either. For Page.ProcessRequest, their documentation actually says:

You should not call this method.
Priceless!

Let us proceed to tread into "you have been warned" territory, though. The problem is you (apparently) aren't just supposed to create a new Page. You should use the PageHandlerFactory. Unfortunately, MS has inconveniently laden that thing with an internal constructor, so you can't make one of those, either.

Thanks to Robert's C# musings for the answer to this one, which is the key to solving this problem. You can't instantiate a class with an internal constructor, directly. But you can make yourself an instance of any class that doesn't call any constructor using a well-hidden GetUninitializedObject method. Since the constructor in the case of this factory is designed only to prevent us from using the factory, and doesn't actually do antyhing useful, not a problem. Once you've got access to the handler factory, the rest falls into place pretty easily. Here's some basic code that maps any path to default.aspx, converting the original path to a query parameter.

public void ProcessRequest(HttpContext context) {

    // the internal constructor doesn't do anything but prevent you from instantiating
    // the factory, so we can skip it.
    
    PageHandlerFactory factory =
        (PageHandlerFactory)System.Runtime.Serialization.FormatterServices
            .GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));

    // you may want to use context.PathInfo - in my case I mapped a wildcard to this
    // handler so it's always blank.

     string newTarget  = "default.aspx"; 
     string newQueryString = "path="+context.Path;
     string oldQueryString = context.Request.QueryString.ToString();
     string queryString = newQueryString + oldQueryString!="" ? 
         "&" + newQueryString :
         "";

     // the 3rd parameter must be just the path to the file target (no querystring).
     // the 4th parameter should be the physical path to the file, though it also
     //   works fine if you pass an empty string - perhaps that's only to override
     //   the usual presentation based on the path?

     var handler = factory.GetHandler(context, "GET",newTarget,
         context.Request.MapPath(context,newTarget));

     // Update the context object as it should appear to your page/app, and
     // assign your new handler.

     context.RewritePath(newTarget, "", queryString);
     context.Handler = handler;

     // .. and done

     handler.ProcessRequest(context);
}

The PageHandlerFactory can certainly be created statically so you don't have the overhead of reflection for every request. The actual "Page" handler must be created each time, though, because it's not marked as IsReusable.

Monday, March 12, 2012

SharpLinter now works with inline scripts in HTML files

It will look for embedded scripts and only validate what's inside legal <script type="text/javascript"></script> blocks. Any file that's not called *.js or *.javascript will be treated this way.

Go get Sharplinter 1.0.2. You can also just download the binary distribution.

SharpLinter is a C# command-line tool for validating javascript. It is highly configurable, can produce output in customized formats, and integrates with Visual Studio. It should work with any editor or tool that supports using external tools to process files. The readme includes instructions for use with Visual Studio, Sublime Text 2, and Textpad.

Wednesday, March 7, 2012

Area groups in ImageMapster

ImageMapster has had the capability to form complex area groups for some time, but never got around to documenting it well. More recently, I added a keys feature that lets you get the keys associated with an area (or another key), and also the ability to pass rendering options directly with the set method. This opens up a lot of possibilities for area manipulation that were possible but not very easy before.

I put together an example showing how area groups can be used, along with these new features, to have a great deal of control over the effects.

Take a look at it on jsFiddle and play around! I'm going to start a library of interactive examples to include on the web site. If you like it, let me know, or put together one that shows your own techniques so I can share it!

Complete documentation is on the project web site.


Intro to the ImageMapster Area Groups example:


This example shows how to use mapKey to create groups that you can use to control sets of areas independently.
  • Each area in the imagemap has a custom attribute data-state. This defines the groups that each area belongs to.
  • The mapKey: 'data-state' option identifies this attribute for the imagemap. The values in the mapKey can be used to select, deselect or highlight areas or groups of areas from code.
  • Areas can belong to more than one group. In this example, New England states belong to three groups: a state code like "ME", the group "new-england", and possibly the group "really-cold".
  • Options can be set for area groups. These options only apply when the group is activated using its group name. Notice if you click a New England state, it's red (like the other states) but when you activate it using "new-england" or "really-cold", it's blue.
  • When you mouse over an area, the first group in the list determines what gets highlighted. In this example, most states are actually defined by more than one area HTML element. The first value of data-state is the state code, ensuring that when you mouse over a state, all the different areas that make it up get highlighted together, even if they aren't connected. New England states and Hawaii are good examples of this (the islands are separate, and the New England states have separate text markers).
  • Areas are separate logical entities. You will notice if you click one of the group links, then highlight a state in New England, it highlights again (in a different color, per the render_select options). This means that area groups are not *directly* a good way to just act as if the user selected each area in the group, but...
  • You can use the keys method to get the primary keys for a group, and the get_options method to get the options, and set them manually. Click "New England As Separate States" below. The "Texas with Custom Options" is a simpler case of setting custom rendering options.

Tuesday, March 6, 2012

SharpLinter now supports Sublime Text 2

Okay, okay. Nothing at all has changed with SharpLinter - github, and Sublime Text 2 supports pretty much anything that generates consistent output with the right config :)

Here's how to add a build system for Sublime Text 2 that uses SharpLinter for Javasript files.
  1. Select Tools -> Build System -> New Build System
  2. Enter the following to create a build config that works against javascript files:
{
    "cmd": ["sharplinter", "-v","-ph","best","*.min.js", "$file"],
    "file_regex": "^(.*?)\\(([0-9]+)\\): ()(.*)$",
    "selector": "source.js"
}

Save it, and your're done. The regex should match SharpLinter's default output and let you use F4 and shift+F4 to navigate any errors within your file.

The "cmd" property for Submlime Text 2 should contain the command followed by any options you want, so this could be as simple as

...
"cmd": ["sharplinter", "$file"],
...

to run SharpLinter with default options against the active file. The options above are just the ones I like to use, which provide verbose output, minify to *.min.js on success, and use the best compression method (usually yui).

Go ahead and set yourself up with SublimeOnSaveBuild and you can have it run every time you save automatically.