Introduction
This article help you to resolved you all query regard to ISAPI filter. This article cover below point…
- Develop the ISAPI re-write engine in ISAPI it self. Not no more .INI. Feel to free to re-write rules as to every URL.
- How to build COM component.
- Development of this component.
- Calling Unmanaged code.
- Unit testing.
- Deployment on production server and after testing on the development as well QA, Staging Server.
Background
My requirements was developed a the ISAPI filter which is going to perform redirection operation to destination URL. Yes this look simple but what if in requirement more clause get added such as
- In database we are storing the Source and destination URL
- Source URL contains re-write rules.
- How to call COM Component from ISAPI Filter.
- No INI files used for evaluate re-write rule.
- Build the re-write engine in ISAPI filter it self.
- Every URL get first get match in the database and if found then parse the re-write expression using re-write engine and redirect to destination URL with all parameters.
- ISAPI filter should be highly optimize because N number of user hitting you web server every second.
- Load banding should be well managed.
Let talk more technical. Lets show you high level diagram, such that we will understand the article.

Process Flow : 
Using the Code
Lets go step by step
Step 1
Lest First create the ISAPI filter. I have created a ISAPI project and given name as “GenericRedirectorISAPI” this project contains below file and folder structure
In above project explore I have included three project
- Re-write rule evaluator engine.
- ISAPI filter project.
- Testing to ISAPI filter project.
Now how to create ISAPI filter. Please refer to isapiredirector.aspx
In the above URL you come to know that how to crate and deploy the ISAPI filer on IIS.Now lest code on point.
Step 2
Add the COM component .tlb file reference and rename the name space for you easy convention. It’s Optional. After successfully importing the TLB now using the it’s namespace such that all method and it’s public member visible in the unmanned code.
Note: C++ is managed code which do communication with native code i.e. executing the un-manage code.
Refer the StdAfx.h in this header file
Step 3
In the ISAPI filter project please refer the file(GenericRedirectorISAPI.cpp) I will main function which do the all the job.
In function variable initialization has been done.
Getting server port and confirming that out going protocol shoud be same as incoming protocol
Now getting server name in order to build the proper redirection URL.
No reading URL value form HTTP protocol header in order to fetch exact value from database and pass to re-write engine and get the destination URL.
Building the Destination URL
Calling COM component and passing the current URL fetch from header and getting destination URL. If COM component return NULL value that mean the for that URL no re-write rule has been define in the database so simply handover that URL to IIS web server to handle to that else build the destination URL and modify the source URL by destination URL by "301 Moved Permanently" .
Summary of GenericRedirectorISAPI.cpp : This CPP file contains many ISAPI filer method and using OnPreProcHeader(…) event I have define business logic such as
- Initialization Of Variable
- Getting source protocol
- Getting source server name
- Reading URL value from Header
- Calling COM component to process the source URL
- Managing Unmanage code
- Creating new header and re-directing to destination if some value return by COM component else handover all things to IIS web server.
#include "stdafx.h"
#include "GenericRedirectorISAPI.h"
CWinApp theApp;
CGenericRedirectorISAPIFilter theFilter;
CGenericRedirectorISAPIFilter::CGenericRedirectorISAPIFilter()
{
}
CGenericRedirectorISAPIFilter::~CGenericRedirectorISAPIFilter()
{
}
BOOL CGenericRedirectorISAPIFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)
{
CHttpFilter::GetFilterVersion(pVer);
pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;
pVer->dwFlags |= SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT | SF_NOTIFY_PREPROC_HEADERS;
pVer->dwFlags |= SF_NOTIFY_ORDER_LOW;
TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];
ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));
_tcscpy(pVer->lpszFilterDesc, sz);
return TRUE;
}
DWORD CGenericRedirectorISAPIFilter::OnPreprocHeaders(CHttpFilterContext* pCtxt,
PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)
{
char urlBuffer[2048];
DWORD urlBuffSize = sizeof(urlBuffer);
CString urlString(urlBuffer);
CString tempUrlString = _T("");
char portBuffer[16];
DWORD portBuffSize = sizeof(portBuffer);
BOOL bResult = pCtxt->GetServerVariable("SERVER_PORT", portBuffer, &portBuffSize);
if( bResult )
{
CString portString(portBuffer);
if( portString == _T("80") )
tempUrlString += _T("http://");
else
tempUrlString += _T("https://");
}
char httpHostBuffer[512];
DWORD httpHostBuffSize = sizeof(httpHostBuffer);
bResult = pCtxt->GetServerVariable("SERVER_NAME", httpHostBuffer, &httpHostBuffSize);
if( bResult )
{
CString httpHostString(httpHostBuffer);
tempUrlString += httpHostString;
}
char refererBuffer[2048];
DWORD refererBuffSize = sizeof(refererBuffer);
bResult=pHeaderInfo->GetHeader(pCtxt->m_pFC, "url", refererBuffer, &refererBuffSize);
if( bResult )
{
CString refererString(refererBuffer);
tempUrlString += refererString;
}
urlString = _T("");
urlString = _T("Location: ");
HRESULT hr = CoInitialize(NULL);
IGenericRedirectorPtr pICalc(__uuidof(GenericRedirector));
_bstr_t destination = _T("");
_bstr_t url = tempUrlString.GetBuffer();
pICalc->MatchRedirect(url,destination.GetAddress());
if(destination.GetBSTR() != NULL)
{
urlString += destination.GetBSTR();
urlString += _T("\r\n\r\n");
pCtxt->ServerSupportFunction (SF_REQ_SEND_RESPONSE_HEADER
, (LPVOID) "301 Moved Permanently"
, (LPDWORD) urlString.GetBuffer()
, (LPDWORD) NULL );
return SF_STATUS_REQ_FINISHED;
}
CoUninitialize();
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
Step 4
Now next step to create re-write parser component
COM Component Creating for parsing re-write rule and database communication
we are creating one component which going take care of the regular expression and database communication. Note : We are creating this COM component in the C#.
COM Component Background
- It accept the browser URL
- Call the stored procedure
- Stored procedure return the matching URL using like query
- Stored all recordset in
StringDictionary
object
- Parse the all source URL againt to passed browser URL and find the exact match
- If exact match not found the then return NULL else return the destination URL.
- Now experience that every URL have it’s own re-write rule.
- Now no INI file.
Define Interface using MarshalAs
and UnmanagedType.BStr
Creating interface which is an Abstract definition of implemented class. This interface contains on method which visible in C++ code as unmanaged code. Keep in mind which creating this definition in the Interface we have to se it return type as UnmanagedType.BStr
with MarshalAs
import the runtime InteropServices class in order to implement the Marshaling and Un-Marshaling concept
using System;
using System.Runtime.InteropServices;
namespace GenericRedirectorDLL
{
[ComVisible(true)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
[Guid("94B7B4DA-9329-4832-B1F9-DA471208D330")]
public interface IGenericRedirector
{
[return : MarshalAs(UnmanagedType.BStr)] string MatchRedirect(
[MarshalAs(UnmanagedType.BStr)] string url);
}
}
Now create the definition class i.e. creating implementation class
We are creating the now Method which going to manage the hole operation. Method MatchRedirect (…)
This method going to perform below mention action.
- Accept the URL as parameter
- Creating
StringDictionary
Object calling other method which going to do the communication with database and after matching [ using like caluse] return the recordset and it get stored in the StringDictionary
object
- Now matching the every return Source URL which contains re-write rules
- Call the other method of of other calls which going to parse the re-write rule and return the passed string.
- if mathich found the with Browser URL then return the it’s Destination URL.
- If no match found then return null
public string MatchRedirect(string url)
{
StringDictionary urlMaps = RedirectorUrlList.UrlMap;
if( urlMaps != null )
{
string dest;
foreach(string source in urlMaps.Keys)
{
dest = UrlMatchHelper.MatchRegExPattern(url,
source, urlMaps[source]);
if( dest != null )
return dest;
}
}
return null;
}
Before creating of above method first we have to
- Implement the define call with Interface
- Set Com visibility true.
- Set it;s GUID
- Set it’s ProgID
- Repeat same step in Interface
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("E53AEF65-EA74-419b-A83B-03656A1E0413")]
[ProgId("GenericRedirector")]
Implementation Class
using System;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace GenericRedirectorDLL
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("E53AEF65-EA74-419b-A83B-03656A1E0413")]
[ProgId("GenericRedirector")]
public class GenericRedirector : IGenericRedirector
{
#region "Constructor"
public GenericRedirector()
{
}
#endregion
#region IGenericRedirector Members
public string MatchRedirect(string url)
{
StringDictionary urlMaps = RedirectorUrlList.UrlMap;
if( urlMaps != null )
{
string dest;
foreach(string source in urlMaps.Keys)
{
dest = UrlMatchHelper.MatchRegExPattern(url,
source, urlMaps[source]);
if( dest != null )
return dest;
}
}
return null;
}
#endregion
}
}
Create Call for reading Source URL from database and stored that URL in the Cache.
using System;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.Caching;
namespace GenericRedirectorDLL
{
[ComVisible(true)]
internal class RedirectorUrlList
{
#region "Member Variables"
private static string CacheKey = "GenericRedirectMap";
private static string ConnectionString =
@"Server=MDYNYCSYNQA01;Database=mdysid;User ID=sid;Password=sid123";
private static int CacheTimeInMinutes = 5;
#endregion
#region "Constructor"
private RedirectorUrlList()
{
}
#endregion
#region "Properties"
public static StringDictionary UrlMap
{
get
{
StringDictionary urlMap = null;
if( HttpContext.Current == null )
urlMap = UrlMapGet();
else
{
HttpContext current = HttpContext.Current;
if( current.Cache[CacheKey] != null )
urlMap =
current.Cache[CacheKey] as StringDictionary;
if( urlMap == null )
{
CacheUrlMap();
urlMap = current.Cache[CacheKey] as StringDictionary;
}
}
return urlMap;
}
}
#endregion
#region "Methods"
#region "CacheUrlMap"
private static void CacheUrlMap()
{
StringDictionary urlMaps = UrlMapGet();
#region "Cache Data"
if( urlMaps.Count > 0 && HttpContext.Current != null )
{
HttpContext current = HttpContext.Current;
current.Cache.Add(CacheKey, urlMaps, null
, DateTime.Now.Add(new TimeSpan(0, CacheTimeInMinutes, 0))
, Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
new CacheItemRemovedCallback(
UpdateUrlMapCacheOnCacheExpiry) );
}
#endregion
}
#endregion
#region "UpdateUrlMapCacheOnCacheExpiry"
public static void UpdateUrlMapCacheOnCacheExpiry(string key,
object value, CacheItemRemovedReason reason)
{
CacheUrlMap();
}
#endregion
#region "UrlMapGet"
private static StringDictionary UrlMapGet()
{
StringDictionary urlMaps = new StringDictionary();
using(SqlConnection conn = new SqlConnection(
ConnectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand("GenericRedirectorsUrl_Get" , conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@pOperation", 1));
using(SqlDataReader reader = cmd.ExecuteReader())
{
if( reader.HasRows )
while( reader.Read())
{
urlMaps.Add(
reader.GetString(reader.GetOrdinal("SourceUrl"))
, reader.GetString(reader.GetOrdinal("DestinationUrl")));
}
}
}
return urlMaps;
}
#endregion
#endregion
}
}
Design he Re-Write Rule Parser Class
This class is heat of the hole ISAPI application. It parse the URL re-write expression and generate the destination.
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
namespace GenericRedirectorDLL
{
[ComVisible(false)]
public class UrlMatchHelper
{
#region "Constructor"
private UrlMatchHelper()
{
}
#endregion
#region "Methods"
public static string MatchRegExPattern(string url, string source, string dest)
{
Regex exp = new Regex(source, RegexOptions.IgnoreCase);
if( exp.IsMatch(url) )
{
Match m = exp.Match(url);
GroupCollection gc = m.Groups;
if( gc.Count > 1 )
{
int counter = 0;
foreach(Group g in gc)
{
if( counter == 0 )
{
counter++;
continue;
}
dest = dest.Replace(
string.Format("{0}{1:0}{2}", "{",
counter++, "}"), g.Value);
}
}
return dest;
}
return null;
}
#endregion
}
}
Step 5
How to deploy the application and make it happning.
Please follow the below mention step in order
- Assign Strong name to GenericRedirectorDLL project
:\.sn –k [stringnameKeyName.snk]
- Assign the strong name key to GenericRedirectorDLL namespace.
- Buld the app application at a time to one by one
- Generate the TBL file of GenericRedirectorDLL.dll
:\>regasm [GenericRedirectorDLL.dll path] /tlb: [GenericRedirectorDLL.tlb path] / codebase
- Now Keep the generated TBL file in same location where ISAPI filter DLL (C++) is get created project.
- Open the Internet Information server
- Select the Web Serve property . You can select hole or one web server property.
- Click on he ISAPI Tab
- Click on ADD button
- Give then name of ISPAI reference Name such as “Genericredirection”
- Select the ISPAI Filter DLL path (C++)
- Reset the server
Database Sturcture
Points of Interest
- Now No more .INI file
- Now customize your code as per you re-write rule.
- Develop the ISAPI re-write engine in ISAPI it self. Not no more .INI. Feel to free to re-write rules as to every URL.
- How to build COM component.
- Development of this component.
- Calling Unmanaged code.
- Unit testing.
- Deployment on production server and after testing on the development as well QA, Staging Server.
My requirements was developed a the ISAPI filter which is going to perform redirection operation to destination URL. Yes this look simple but what if in requirement more clause get added such as
- In database we are storing the Source and destination URL
- Source URL contains re-write rules.
- How to call COM Component from ISAPI Filter.
- No INI files used for evaluate re-write rule.
- Build the re-write engine in ISAPI filter it self.
- Every URL get first get match in the database and if found then parse the re-write expression using re-write engine and redirect to destination URL with all parameters.
- ISAPI filter should be highly optimize because N number of user hitting you web server every second.
- Load banding should be well managed.
History
If any problem then please contact me any time on +91 9773596947 or +91 9869566849
==========================================
Column Name Data type Max Length
==========================================
Source Varchar 255
Destination Varchar 255
MatchOrder Number
(Primary Key) 18,0
isActive Binary 1