Comparison Of HttpHandler And HttpModule With Middleware

What is Middleware and how is it important ?

 
In Asp.net core, we have seen the introduction of middleware, which replaces HttpHandler and HttpModule from standard asp.net web based applications.
 
Before understanding middleware, let's understand what HttpHandler and HttpModule used to do in standard ASP.net web applications.
 
 

HttpHandler

 
The purpose of HttpHandler in standard web aplications is to handle the incoming request and process it and detect the particular resource depending on the extension.
 
For example the request is like - http://localhost/mvc/mvc.html - here this request is asking for a resource with .html extension. So this request is processed to produce the resource as a response.
 
If the request is like - http://localhost/mvc/showContact.cs -here this request is asking for resources with the extension .cs file. So this request is processed to produce the resource as a response. Now in this case httphandler acts on this request to produce a dynamic web page as a response.
 
Any HttpHandler implements IHTTPHandler
 
IHTTPHandler has 1 method and 1 property,
  • IsReusable
  • ProcessRequest which takes parameter, HttpContext object as paramter.

ProcessRequest

 
This method take the HttpContext as a paramater. HttpContext contains many properties, among them 2 important are request and response. Now using this request property, we can get all the imformation sent via HttpRequest such as domain name, query string values, request meta values and so on.
 
As we get information from request property of HttpContext object we can process the request and provide the resource as a response in this method itself.
 
Now once this handler is made, this handler needs to be registered in the application webcofig under system.web. Here we can set the verb and type of extension in a request to which a particular handler should act and process the request.
 
HttpHandler is also called Extension Based Request preprocessor.
 

HttpModule

 
When a request is raised for a resource to a web application, there are some events which take place before the request is ultimately processed and resource is returned. These events are the ones which do some checks on the request before it can be processed and the resource is produced fresh from the web server.
 
Now here HttpModules work on these events and perform some extra logic on the request or with the request . HttpModule can be attached to any event on the basis of the business requirement.
 
The main events that the request goes through are as follows:
 
BeginRequest, AuthenticateRequest, AuthorizeRequest, PreRequesthandlerRexecute, PostRequesthandlerExecute, EndRequesst.
 
These are the events where Modules can be attached to inject some extra logic for this event, acting on a request.
 
Let's see the structure of HTTPModule. HttpModule implements IHttpModule. IHttpModule has 2 methods - init(HttpApplication), Dispose().
 
HttpApplicationobject exposes all the events of the application which will act on the request before it's processed by the HttpHandler.
 
Below is the example of Custom HttpModule acting on various events of the web application:
  1. using System;  
  2. using System.Web;  
  3.   
  4. namespace SmartStore.Web  
  5. {  
  6.     public class MyModule : IHttpModule  
  7.   
  8.     {  
  9.         public void Dispose()  
  10.   
  11.         {  
  12.            throw new NotImplementedException();  
  13.         }  
  14.         public void Init(HttpApplication context)  
  15.   
  16.         {  
  17.             context.AuthenticateRequest += Context_AuthenticateRequest;  
  18.         }  
  19.         private void Context_AuthenticateRequest(object sender, EventArgs e)  
  20.   
  21.         {  
  22.             throw new NotImplementedException();  
  23.         }  
  24.     }  
  25. }    
As we can see above that AuthenticateRequest event of the application is attached with the MyModule. The event objects gets the void function Context_AuthenticateRequest with parameter object sender, EventArgs e.
 
Now an application's event can have more than one module acting on it. Whereas only one HttpHandler processes on a request specific to a type. One type of request cannot be processed by 2 different HttpHandlers. This is a major difference between HttpHandler and HttpModule.
 
Now as we can see that HttpModule acts on the application events, HttpModule is also called Event based preprocessing unit.
 
So this is the basic background of Httphandler and HttpModule.

So in short,
 

HTTPHandlers

  • Classes that implement IHttpHandler
  • Used to handle request with a given file name or extensions
  • To be configured in Web.Config

HttpModules

  • Classes that implement IHttpModule
  • Invoked for every request if it's attached to any application event.
  • It can be used to short circuit the request if it fails in any passing criteria in any event. It means that at any application event , if the request fails to meet the business criteria defined by the HttpModule, then request will not be passed to the next event in that application and will return an appropiate response back to the request.
  • It can create its own HttpResponse
  • To be configured in Web.Config.
Now we will see how these two are replaced by the Middleware in ASP.NET CORE.
 
Middleware in asp.net core has replaced both handlers and modules. First we need to understand what is actually a middleware and how it functions in detail.
 
So Middleware is a component in asp.net core through which every request will pass and be processed as per the need. Middleware can short circuit the processing of the request and pass on the response back to requestor, if a business need is not fulfilled by the requestor, or it can pass the control of the request to the next middleware for processing it and ultimately get a response back to the requestor.
 
Now middleware has replaced both HttpHandler and HttpModule, thus it's not needed to configure these middleware in separate files like in web.config.
 
The configuring of middleware is done in the code base of the ASP.Core application.
 
Typically these middlewares can be custom made or can be downloaded from Nuget space as per the business rule.
 
So all these middlewares form the request pipeline all together, which processes the request in order and sends the response in reverse order of the request processing. This means if middleware passes through middleware 1 -> middleware 2 -> middleware 3 -> middleware 4 and then get response, then response will be sent in a reverse way through middleware4 -> middleware3 -> middleware2 -> middleware1 and then goes to the requestor.
 
Below is a Pictorial representation of the Middleware processing from Request to Response in an ASP.CORE application .
 
 
 
Now to understand how middleware works from a coding point of view, we need to understand the how the ASP.net Core application starts. Asp.net Application is a console application which has a main function. This main function's responsibility is to host the application and configure all the necessary middleware to process all requests that come in the application and can be sent back to the requestor.
 
This console application is also responsible for creating all the depending injection configuration , which will be needed to process the request by the middleware.
 
Now to configure middleware and create a request pipeline, we need a startup class where a "Configure" method is setup. A StartUp class is the class where all the middlewares are registered. This startup class is initialized and instantiated in the main function. A startup class must have Configure() method as here the request pipeline is set for the application, using all the middleware. The startup class also has ConfigureService(), optional method where all the services are registered which will be used by the middleware to process requests and inject the services as required. These service are also called the dependencies which needs to be injected in request processing components by the middleware.
 
So this was the brief explanation of how Middleware works and what are the methods it holds
 
Now let's understand how this middleware replaces both HTTPhandler and HTTPModule.
 

Migrating HttpHandler Code to Middleware

 
Lets define a scenario where HttpHandler will process a request which has an extension .report. In traditional Asp.net application following are the ways to write it,
  • First we need to create a HttpHandler which will process the .report extension request.
  • This handler needs to be configured in the web.config file.
Below is the code of the HttpHandler,
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;   
  5. namespace SmartStore.Web.HttpHandler  
  6.   
  7. {  
  8.     public class ReportHttpHandler : IHttpHandler  
  9.     {  
  10.         public bool IsReusable  
  11.         {  
  12.             get  
  13.             {  
  14.                 return true;  
  15.             }  
  16.         }  
  17.         public void ProcessRequest(HttpContext context)  
  18.   
  19.         {  
  20.             //calls a method to check for .report extension in request;  
  21.             if(HasReportExtention(context))  
  22.             {  
  23.                 //generates a response for request  
  24.                 var response = GenerateExtension(context);  
  25.                 //sets the reposne in response object of the httpContext  
  26.                 context.Response.Output.WriteLine(response);  
  27.             }  
  28.         }  
  29.         private string GenerateExtension(HttpContext context)  
  30.         {  
  31.             // generate response if the request url has .report extension  
  32.             string response = "This url has .report extension";  
  33.             return response;  
  34.         }  
  35.         private bool HasReportExtention(HttpContext context)  
  36.   
  37.         {  
  38.             string url = context.Request.Url.ToString();  
  39.             return applicationHttpContext.Request.Url.ToString().EndsWith(".report");  
  40.         }  
  41.     }  
  42. }  
Below is the code to register the HttpHandler in webconfig,
  1. <system.web>  
  2.     <httpHandlers>  
  3.       <add verb="*" path=".report" type="SmartStore.Web.HttpHandler.ReportHttpHandler"/>  
  4.     </httpHandlers>  
  5.   </system.web>  
The above code registers the Httphandler for the request having .report extension.
 
Now let's see how this handler is replaced. The middleware below is the code of custom middleware. It's a short circuit middleware because this middle will only process a request which has an extension " .report".
  1. using Microsoft.AspNetCore.Http;  
  2. using System.Threading.Tasks;   
  3.   
  4. namespace MVCCORECRUD.Middleware  
  5. {  
  6.     public class ReportMiddleware  
  7.     {  
  8.         private RequestDelegate _nextTask;  
  9.         public ReportMiddleware(RequestDelegate nextTask)  
  10.         {  
  11.             _nextTask = nextTask;  
  12.         }  
  13.         public async Task Invoke(HttpContext context)  
  14.   
  15.         {  
  16.            string content = GenerateResponseExtension(context);  
  17.             await context.Response.WriteAsync(content);  
  18.         }   
  19.         private string GenerateResponseExtension(HttpContext context)  
  20.   
  21.         {  
  22.             string resource = "This url has .report extension";  
  23.             return resource;  
  24.         }  
  25.     }  
  26. }  
Below is the code of registering the middleware in the startup class,
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.AspNetCore.Mvc;  
  9. using Microsoft.Extensions.Configuration;  
  10. using Microsoft.Extensions.DependencyInjection;  
  11. using MVCCORECRUD.Middleware;   
  12. namespace MVCCORECRUD  
  13.   
  14. {  
  15.     public class Startup  
  16.     {  
  17.         public Startup(IConfiguration configuration)  
  18.          {  
  19.             Configuration = configuration;  
  20.         }   
  21.         public IConfiguration Configuration { get; }   
  22.   
  23.         // This method gets called by the runtime. Use this method to add services to the container.  
  24.   
  25.         public void ConfigureServices(IServiceCollection services)  
  26.         {  
  27.             services.Configure<CookiePolicyOptions>(options =>  
  28.             {  
  29.                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.  
  30.                 options.CheckConsentNeeded = context => true;  
  31.                 options.MinimumSameSitePolicy = SameSiteMode.None;  
  32.             });   
  33.             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
  34.   
  35.         }   
  36.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  37.   
  38.         public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
  39.         {  
  40.             if (env.IsDevelopment())  
  41.             {  
  42.                 app.UseDeveloperExceptionPage();  
  43.             }  
  44.             else  
  45.             {  
  46.                 app.UseExceptionHandler("/Home/Error");  
  47.             }   
  48.             app.UseStaticFiles();  
  49.   
  50.             app.UseCookiePolicy();  
  51.            app.UseMiddleware<ReportMiddleware>();  
  52.             app.UseMvc(routes =>  
  53.             {  
  54.                 routes.MapRoute(  
  55.                     name: "default",  
  56.                     template: "{controller=Home}/{action=Index}/{id?}");  
  57.             });  
  58.         }  
  59.     }  
  60. }  
Now please follow the code in red. This is the process of how you register a custom middleware in the request pipeline of an application in ASP.net core. Now the problem is, any request that is passed in this application will follow this request pipeline, which means, it will also be processed in custom "ResportMiddleware", which was only meant for the request with the ".report" extension. In asp.net standard, this filtering was itself done by the HttpHandler where check was maintained of the extension in the request URL.
 
But in this case it's different. In custom middleware you cannot provide such a check.
 
Luckily Asp.net Core has provided a branching request processing method which helps in processing a request depending on a condition. This is an extension method exposed by the IApplicationBuilder interface. Its MapWhen().
 
Below is the changed code of the startup class where a request is branched depending on the extension ".report"
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.AspNetCore.Mvc;  
  9. using Microsoft.Extensions.Configuration;  
  10. using Microsoft.Extensions.DependencyInjection;  
  11. using MVCCORECRUD.Middleware;  
  12. namespace MVCCORECRUD  
  13.   
  14. {  
  15.     public class Startup  
  16.     {  
  17.         public Startup(IConfiguration configuration)  
  18.         {  
  19.             Configuration = configuration;  
  20.         }  
  21.         public IConfiguration Configuration { get; }  
  22.   
  23.         // This method gets called by the runtime. Use this method to add services to the container.  
  24.         public void ConfigureServices(IServiceCollection services)  
  25.         {  
  26.             services.Configure<CookiePolicyOptions>(options =>  
  27.             {  
  28.                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.  
  29.                 options.CheckConsentNeeded = context => true;  
  30.                 options.MinimumSameSitePolicy = SameSiteMode.None;  
  31.             });   
  32.             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
  33.   
  34.         }  
  35.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  36.   
  37.         public void Configure(IApplicationBuilder app, IHostingEnvironment env)         
  38.   
  39.             {  
  40.             if (env.IsDevelopment())  
  41.             {  
  42.                 app.UseDeveloperExceptionPage();  
  43.             }  
  44.             else  
  45.             {  
  46.                 app.UseExceptionHandler("/Home/Error");  
  47.             }  
  48.             app.UseStaticFiles();  
  49.             app.UseCookiePolicy();  
  50.    
  51.               app.UseMiddleware<ReportMiddleware>(); - replaced by the code below  
  52.   
  53.             app.MapWhen(context => HasReportExtention(context),  
  54.   
  55.                     appBranch =>  
  56.                     {  
  57.                         appBranch.UseMiddleware<ReportMiddleware>();  
  58.                     }  
  59.                 );  
  60.             app.UseMvc(routes =>  
  61.             {  
  62.                 routes.MapRoute(  
  63.                     name: "default",  
  64.                     template: "{controller=Home}/{action=Index}/{id?}");  
  65.             });  
  66.         }  
  67.         private bool HasReportExtention(HttpContext context)  
  68.   
  69.         {  
  70.             return context.Request.Path.ToString().EndsWith(".report");             
  71.         }  
  72.     }  
  73. }  

Migrating HttpModule Code to Middleware

 
As discussed HttpModule is the event based preprocessing logic on a request that passes through all the events in the Standard asp.net web application before it can be processed through the HttpHandler.
 
Let's look into the code for HttpModule,
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;   
  5. namespace MVCSTANDARDCRUD.Module  
  6.   
  7. {  
  8.     public class ReportModule : IHttpModule  
  9.     {  
  10.         public void Dispose()  
  11.         {  
  12.             throw new NotImplementedException();  
  13.        }   
  14.         public void Init(HttpApplication context)  
  15.   
  16.         {  
  17.             context.AuthenticateRequest += Context_AuthenticateRequest;  
  18.         }   
  19.         private void Context_AuthenticateRequest(object sender, EventArgs e)  
  20.   
  21.         {  
  22.             var applicationHttpContext = ((HttpApplication)sender).Context;  
  23.             if(!IsRequestAuthentic(applicationHttpContext))  
  24.             {  
  25.                 applicationHttpContext.Response.Write("Please login before accessing the report");  
  26.             }  
  27.         }  
  28.         private bool IsRequestAuthentic(HttpContext applicationHttpContext)  
  29.   
  30.         {  
  31.             return applicationHttpContext.User == null ? false : true;  
  32.         }  
  33.     }  
  34. }  
The above code creates a custom HttpModule which authenticates the request. It checks whether the request has valid User information. If not then it asks the User to provide the valid User information while generating the request. To provide the user information in the request, in this case, the user needs to login into the system.
 
Now as the Custome HttpModule is ready and it's attached to the " AuthenticateRequest " application event, it needs to be registered into the system. This is done in web.config file of standard Asp.net application.
 
Below is the code,
  1. <system.webServer>  
  2.     <modules>  
  3.       <add name="ReportModule" type="MyApp.Modules.ReportModule"/>  
  4.     </modules>  
  5.   </system.webServer>  
Now let's see how HttpModule is replaced by the Middleware in the Asp.net Core.
  1. using Microsoft.AspNetCore.Http;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;   
  6. namespace MVCCORECRUD.Middleware  
  7.   
  8. {  
  9.     public class ReportModuleMiddleware  
  10.     {  
  11.         private RequestDelegate _nextTask;  
  12.         public ReportModuleMiddleware(RequestDelegate nextTask)  
  13.         {  
  14.             _nextTask = nextTask;  
  15.         }  
  16.        public async Task Invoke(HttpContext context)  
  17.   
  18.         {             
  19.             await context.Response.WriteAsync("Please login before accessing the website");  
  20.         }  
  21.     }  
  22. }  
Now we need to register this middleware before the middleware which handles any request. This is also a short-circuit middleware, which if no user is found, will pass the response as defined in the middleware. This will be added into a request pipeline in Configure method in startup class as shown below.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.AspNetCore.Mvc;  
  9. using Microsoft.Extensions.Configuration;  
  10. using Microsoft.Extensions.DependencyInjection;  
  11. using MVCCORECRUD.Middleware;   
  12.   
  13. namespace MVCCORECRUD  
  14. {  
  15.     public class Startup  
  16.     {  
  17.         public Startup(IConfiguration configuration)  
  18.         {  
  19.             Configuration = configuration;  
  20.         }  
  21.         public IConfiguration Configuration { get; }  
  22.   
  23.         // This method gets called by the runtime. Use this method to add services to the container.  
  24.         public void ConfigureServices(IServiceCollection services)  
  25.         {  
  26.             services.Configure<CookiePolicyOptions>(options =>  
  27.             {  
  28.                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.  
  29.                 options.CheckConsentNeeded = context => true;  
  30.                 options.MinimumSameSitePolicy = SameSiteMode.None;  
  31.             });   
  32.   
  33.             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
  34.   
  35.         }  
  36.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  37.   
  38.         public void Configure(IApplicationBuilder app, IHostingEnvironment env)         
  39.   
  40.         {  
  41.             if (env.IsDevelopment())  
  42.             {  
  43.                 app.UseDeveloperExceptionPage();  
  44.             }  
  45.             else  
  46.             {  
  47.                 app.UseExceptionHandler("/Home/Error");  
  48.             }  
  49.             app.UseStaticFiles();  
  50.   
  51.             app.UseCookiePolicy();  
  52.             app.MapWhen(context => !IsRequestAuthenticated(context),  
  53.                 appBranch =>  
  54.                 {  
  55.                     appBranch.UseMiddleware<ReportModuleMiddleware>();  
  56.                 });  
  57.             app.MapWhen(context => HasReportExtention(context),  
  58.   
  59.                 appBranch =>  
  60.                 {  
  61.                     appBranch.UseMiddleware<ReportMiddleware>();  
  62.                 });  
  63.             app.UseMvc(routes =>  
  64.   
  65.             {  
  66.                 routes.MapRoute(  
  67.                     name: "default",  
  68.                     template: "{controller=Home}/{action=Index}/{id?}");  
  69.             });  
  70.         }  
  71.         private bool IsRequestAuthenticated(HttpContext context)  
  72.   
  73.         {  
  74.             return context.User == null ? false : true;  
  75.         }  
  76.         private bool HasReportExtention(HttpContext context)  
  77.   
  78.         {  
  79.             return context.Request.Path.ToString().EndsWith(".report");             
  80.         }  
  81.     }  
  82. }  
In the code in red is where the request's authenticity is tested. If it's not authenticated then the middleware will short circuit the flow of request and will pass response to the requestor.
 
There are other points of comparisons of HttpModule and Httphandler of Asp.net standard applicationwith Middleware in Asp.net core. These points are taken from the links - https://docs.microsoft.com/en-us/aspnet/core/migration/http-modules?view=aspnetcore-3.1
 
Middleware are simpler than HTTP modules and handlers,
  • Modules, handlers, Global.asax.cs, Web.config (except for IIS configuration) and the application life cycle are gone.
  • The roles of both modules and handlers have been taken over by middleware
  • Middleware are configured using code rather than in Web.config
  • Pipeline branching lets you send requests to specific middleware, based on not only the URL but also on request headers, query strings, etc.
Middleware are very similar to modules,
Middleware and modules are processed in a different order,
  • Order of middleware is based on the order in which they're inserted into the request pipeline, while order of modules is mainly based on application life cycle events
  • Order of middleware for responses is the reverse from that for requests, while order of modules is the same for requests and responses
The diagram has been taken from the site,
 
https://docs.microsoft.com/en-us/aspnet/core/migration/http-modules?view=aspnetcore-3.1
https://www.tutorialsteacher.com/core/aspnet-core-middleware