Click here to Skip to main content
15,880,796 members
Articles / Web Development / ASP.NET

ASP.NET Advanced Generic Handler ASHX

Rate me:
Please Sign up or sign in to vote.
4.74/5 (49 votes)
9 Jun 2013CPOL5 min read 320.2K   10.2K   138   60
Take your Generic Handlers to the next level...

GitHub repository now available

I will also be updating the project progress on CP but now you can also fork it on GitHub

 Introduction

In ASP.NET, we have something that is usually overlooked that is called Generic Handlers. I see a lot o f people using pages to process AJAX requests when we can use this much less expensive endpoint. This is an completely worked out Generic Handler that truly knows how to handle your http (AJAX and not) requests. 

Background   

For a long time I used plain Generic Handlers (ASHX files) to handle my AJAX requests but it felt stupid and painful. I mean, the functionality was there but the whole process of handling the requests wasn't straight forward. So I made a list of the things I would like to have and now it's already a lot more than that.

  • NEW v2.5! 
    • Fully supports collections as requests arguments 
      • Nested collections are fully supported
      • Native and Complex item types supported
    • Improved support for complex types as method arguments
    • Improved HTTP verbs filter 
    • New HTTP verbs filter attributes (DELETE and PUT) 
    • Improved controller Help (more to come soon) 
    • Improved performance (still working to further improve this) 
      • Performance improvements are always a work in progress. From the simple tests I've done this controller already performs at the same level as the ASP.net MVC controllers.
    • New PerformanceTest.aspx added to main demo project
    • New ASP.net MVC project created for performance comparison with MVC controllers
      • This project isn't added to the solution. Add if you want to use it.
    • Small refactoring and improvements all around
  • NEW v2.0!
    • OnMethodInvoke and AfterMethodInvoke virtual methods
      • These allow the possibility to intersect the execution before and after the method call  
    • public void SetResponseContentType(ResponseContentTypes value)
      • Easy way to set the response content type from the enum
    • Specify supported HTTP verbs by handler and by method
      • Now we can specify if a method only supports certain HTTP verbs by simply decorating the method with the related Attribute
    • Default methods for main HTTP verbs (GET, POST, PUT and DELETE)
      • If no method is passed the handler will call the method related with the Request HTTP verb. 
      • These methods can me overrided to implement your logic.
    • Response serialization improvements  (JSON and XML)
      • You can now use the returntype parameter on the Request to specify how you want your response data without having to modify your code. 
      • The default is JSON but you can also pass XML or HTML.
      • The default serialization can be disabled on method scope.
    • Performance optimization 
  • v1.1
    • Support for complex objects as method argument
      • Now you can put your custom classes on the handler methods arguments.
      • Automatically hydrates the class and all its nested types!!
  • v1.0
    • Standard way to parse the query string
    • Transparently handle multiple methods within the same handler
    • Support methods with multiple typed arguments, not just strings
    • Support Methods that receive lists as an argument
    • Support passing less arguments than the method is expecting (like optional parameters)
    • Transparently reply either POSTs or GETs
    • Support default object serialization to JSON but still let me override it on each method
    • Return application/json by default but still let me override it on each method
    • Support jQuery $.ajax request
    • Support request by query string (URL right on the browser)
    • A way to visualize the methods the handler supports (like webservices do)
    • Extensible 

Using the code  

I'm working to improve this documentation.
Until then, all you need to know is on the Default.aspx of the demo project.

List the Handler methods 

I've provided a very basic way of listing the methods the Handler exposes. This is specially useful to test if the handler is working correctly (like on webservices). Do do so just append ?help at the end of the handler URL:

http://localhost/mydemohandler.ashx?help

Calling the Handler from the browser URL

Using this handles is very simple:

  1. Create a new Generic Handler
  2. Clear everything inside the handler class
  3. Inherit from my Handler class
  4. DONE! Now you only need to add your methods.

Let's create a very simple example that receives a name and returns a string (see on the project).

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using App.Utilities.Web.Handlers;

namespace CodeProject.GenericHandler
{
	public class MyFirstHandler : BaseHandler
	{
		// I don't bother specifying the return type, it'll be serialized anyway
		public object GreetMe(string name) 
		{
			return string.Format("Hello {0}!", name);
		}
	}
}

To call this method through a URL use:

C#
MyFirstHandler.ashx?method=GreetMe&name=AlexCode

AJAX Request using jQuery

If you want to use jQuery AJAX method you just need to know the object the handler is expecting to get. On the data property of the $.ajax request you must pass something like:

{ method: 'The method you want to call', args: { the arguments to pass } }
Be aware that everything is case sensitive!
$.ajax({
	url: 'MyFirstHandler.ashx',
	type: 'GET',
	data: { method: 'GreetMe', args: { name: 'AlexCode'} },
	success: function (data) {
		alert(data);
	}
});

Writing a method that returns HTML

Like I said on my intention points above, I need to have some methods that return whatever I want like HTML, XML, images, files, etc... The default behavior of the handler is to return JSON so, by method, we need to explicitly say that we want to handle things our way. For that just use these lines anywhere within the method:

SkipContentTypeEvaluation = true;	
SkipDefaultSerialization = true;

// you can specify the response content type as follows
context.Response.ContentType = "text/html";
Lets see an example on how we could write a method on the handler that returns HTML:
public object GiveMeSomeHTML(string text)
{
	StringBuilder sb = new StringBuilder();
	sb.Append("<head><title>My Handler!</title></head>");
	sb.Append("<body>");
	sb.Append("
This is a HTML page returned from the Handler
");
	sb.Append("
The text passed was: " + text + "
");
	sb.Append("</body>");

	context.Response.ContentType = "text/html";
	SkipContentTypeEvaluation = true;
	SkipDefaultSerialization = true;

	return sb.ToString();
}

Optional Parameters and nullable types

All parameters in the methods are optional. If they're not passed their default value is assigned. Also all parameters can be nullable. In this case the default value will be null.

Support for complex types

Say you have a JSON object and a class server side that maps it. Hydrating this class server side is a pain. Usually we pass each property as an argument of the method on the Handler, instantiate a new instance of the class and set the properties one by on... a pain right? NO MORE! This handler now supports automatic class instance creation and property set. Something I like to call Object Hydratation! Smile | <img src= " /> And there's more! If this class have properties that also expose other custom classes they will be hydrated too!! Just make sure all classes have a public default constructorr

// Now you can have method like this
public object SendPersonData(Person person)
{
return person.Name;
}

// your ajax call object would be something like
...
data: { 
     method: 'SendPersonData', 
     args:{ 
          person: { Name: 'Alex' } 
          } 
      }
... 

Please have a look at the attached code sample for more examples. 

Points of Interest

I can say that this handler already saved me a good amount of development and maintenance hours. Currently all my AJAX requests point to a method on an handler like this.

History

  • v2.5 - The so awaited support for collections is here! Full details above!
  • v2.0 - New features and performance enhancements
  • v1.1 - Added support for complex arguments  
  • v1.0 - The first wide open version  
  • This is a work in progress, I keep improving it regularly.

I have no doubt that you'll try to use this in scenarios I haven't predicted. Please send me your requests and desires, I'll do my best to implement them. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Switzerland Switzerland
Senior IT Consultant working in Switzerland as Senior Software Engineer.

Find more at on my blog.

Comments and Discussions

 
QuestioncURL query? Pin
bradut18-Oct-15 18:35
professionalbradut18-Oct-15 18:35 
AnswerRe: cURL query? Pin
AlexCode19-Oct-15 5:49
professionalAlexCode19-Oct-15 5:49 
GeneralRe: cURL query? Pin
bradut19-Oct-15 9:41
professionalbradut19-Oct-15 9:41 
GeneralRe: cURL query? Pin
AlexCode19-Oct-15 22:30
professionalAlexCode19-Oct-15 22:30 
GeneralRe: cURL query? Pin
bradut20-Oct-15 6:39
professionalbradut20-Oct-15 6:39 
GeneralRe: cURL query? Pin
AlexCode20-Oct-15 9:01
professionalAlexCode20-Oct-15 9:01 
Questionjson array to generic handler Pin
snow_cap3-Jul-14 11:13
snow_cap3-Jul-14 11:13 
AnswerRe: json array to generic handler Pin
AlexCode4-Jul-14 2:59
professionalAlexCode4-Jul-14 2:59 
GeneralMy vote of 1 Pin
Member 1043844613-Feb-14 0:57
Member 1043844613-Feb-14 0:57 
GeneralRe: My vote of 1 Pin
AlexCode13-Feb-14 22:56
professionalAlexCode13-Feb-14 22:56 
GeneralRe: My vote of 1 Pin
Member 1043844613-Feb-14 23:39
Member 1043844613-Feb-14 23:39 
GeneralRe: My vote of 1 Pin
AlexCode16-Feb-14 21:51
professionalAlexCode16-Feb-14 21:51 
GeneralMy vote of 1 Pin
Matty2213-Jun-13 21:06
Matty2213-Jun-13 21:06 
GeneralRe: My vote of 1 Pin
AlexCode13-Jun-13 21:25
professionalAlexCode13-Jun-13 21:25 
GeneralRe: My vote of 1 Pin
AbsCode4-Aug-13 1:47
AbsCode4-Aug-13 1:47 
GeneralMy vote of 4 Pin
Sniper909-Jun-13 23:09
Sniper909-Jun-13 23:09 
QuestionJson.NET? Pin
ahagel28-May-13 18:24
professionalahagel28-May-13 18:24 
AnswerRe: Json.NET? Pin
AlexCode28-May-13 22:15
professionalAlexCode28-May-13 22:15 
This is an interesting question that made me think a bit D'Oh! | :doh:

We are actually passing JSON into the controller so parsing it back to an object should be straight forward, specially using a neat tool like JSON.net, right?
I must confess that I had to test it before replying but at the end I couldn't find a way to do this using either JSON.net or any other helper tool and here's why:

Simple GET request
Lets forget JSON for now and do a normal GET request to the controller:
http://localhost/mycontroller.ashx?method=MyMethod&name=Alex&age=36

This will be received by a method on the controller with the following signature:
public object MyMethod(String name, int age) { return "something"; }

So one of the main functionalities of this Advanced controller is to actually automatically call the method we want and parse the request arguments into the method's arguments. You can test this on the demo project.

Now with JSON and AJAX HTTP GET request
If you do an AJAX call to the controller and pass the following object:
{method:'MyMethod', args:{name:'Alex',age:36}}

You'll basically get the same result as the previous request but a bit different:
http://localhost/mycontroller.ashx?method=MyMethod&args%5Bname%5D=Alex&args%5Bage%5D=36

// In a readable format looks like:
method=MyMethod
args[name]=Alex
args[age]=36

The 'method' is used internally to search for the method to invoke within the controller.
the rest are the arguments that will be passed to that method. These arguments also need to be casted to the correct type expected by the method.

And it gets worse...
If for example we are passing an array of objects we can get something like:
args[person[0].name]=Alex
args[person[0].age]=36
args[person[1].name]=John
args[person[1].age]=28


If the collection is nested in an object type:
args[people.person[0].name]=Alex
args[people.person[0].age]=36
args[people.person[1].name]=John
args[people.person[1].age]=28

This last example samples a people class that has a person class collection property.
We need to create new instances of each type and hydrate the objects.

Collection types can also be basic Arrays or List<>, which need to be created and handled differently.

Beyond HTTP GET
POST, PUT and DELETE pass data differently. They don't use the QueryString.
Instead the pass all the data in the request params which need to be handle slightly differently.

To wrap all up
So, all this explanation to say that AFAIK there's no way, out of the box, to translate this request information to objects. And this was my main goal when I started this project.

I can, in fact, use JSON.net to serialize the response.
I didn't do it already because I didn't want to add a reference to an external assembly or increase the size of this one by adding the JSON.net source code into it.

Hope I managed to explain my intentions and decisions.
If anyone knows a better way to do this I'm all ears!!! Big Grin | :-D

Cheers!!
GeneralMy vote of 1 Pin
Ali Reza Barkhordari27-May-13 10:39
Ali Reza Barkhordari27-May-13 10:39 
GeneralRe: My vote of 1 Pin
AlexCode27-May-13 10:52
professionalAlexCode27-May-13 10:52 
GeneralRe: My vote of 1 Pin
Ali Reza Barkhordari28-May-13 6:00
Ali Reza Barkhordari28-May-13 6:00 
GeneralRe: My vote of 1 Pin
AlexCode28-May-13 7:05
professionalAlexCode28-May-13 7:05 
GeneralRe: My vote of 1 Pin
HaBiX29-May-13 7:59
HaBiX29-May-13 7:59 
GeneralRe: My vote of 1 Pin
AlexCode29-May-13 20:47
professionalAlexCode29-May-13 20:47 
GeneralRe: My vote of 1 Pin
HaBiX29-May-13 21:46
HaBiX29-May-13 21:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.