Search code examples
asp.netiis-7httpmodule

Is it possible to call the IIS 7+ setUrl API from a managed HttpModule?


What I basically need is to manage a large set of URL rewrites and redirects. The redirects are easy to do but the rewrites should be proxyed through the ARR proxy in IIS. As far as I can tell the IIS rewrite module uses the native setUrl API in IIS to get the ARR proxy to forward the request. I'm not up to the task of writing a native module so I thought it might be possible to write a managed module to do the same.

@CarlosAg: Just to expand my comment on your answer. Heres the rules and providers I need to set up to make the rewrite module do what I want. Is it just me or doesn't it seem a little like manipulating the rewrite module into doing something it wasn't supposed to do?

<rewrite>
  <providers>
    <provider name="CustomRewrite" type="MyRewriteProviders.RewriteProvider, MyRewriteProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e72501f8a0edfe78" />
    <provider name="CustomRedirectTemporary" type="MyRewriteProviders.RedirectTemporaryProvider, MyRewriteProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e72501f8a0edfe78" />
    <provider name="CustomRedirectPermanent" type="MyRewriteProviders.RedirectPermanentProvider, MyRewriteProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e72501f8a0edfe78" />
  </providers>
  <rules>
    <clear />
    <rule name="Set Http">
      <match url="." />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
      <serverVariables>
        <set name="RequestProtocol" value="http" />
      </serverVariables>
      <action type="None" />
    </rule>
    <rule name="Set Https">
      <match url="." />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{HTTPS}" pattern="ON" />
      </conditions>
      <serverVariables>
        <set name="RequestProtocol" value="https" />
      </serverVariables>
      <action type="None" />
    </rule>
    <rule name="Custom Rewrite">
      <match url="(.*)" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{CustomRewrite:{RequestProtocol}://{HTTP_HOST}/{URL}}" pattern="(.+)" />
      </conditions>
      <action type="Rewrite" url="{C:1}" logRewrittenUrl="true" />
    </rule>
    <rule name="Custom Redirect (Temporary)" stopProcessing="true">
      <match url="(.*)" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{CustomRedirectTemporary:{RequestProtocol}://{HTTP_HOST}/{URL}}" pattern="(.+)" />
      </conditions>
      <action type="Redirect" url="{C:1}" redirectType="Found" appendQueryString="false" />
    </rule>
    <rule name="Custom Redirect (Permanent)" stopProcessing="true">
      <match url="(.*)" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{CustomRedirectPermanent:{RequestProtocol}://{HTTP_HOST}/{URL}}" pattern="(.+)" />
      </conditions>
      <action type="Redirect" url="{C:1}" appendQueryString="false" />
    </rule>
  </rules>
</rewrite>

And the provider's code:

using System;
using System.Collections.Generic;
using Microsoft.Web.Iis.Rewrite;

namespace MyRewriteProviders
{
    public enum ActionType
    {
        Rewrite,
        RedirectPermanent,
        RedirectTemporary
    }

    public abstract class BaseProvider : IRewriteProvider
    {
        protected abstract ActionType Action { get; }

        public void Initialize(IDictionary<string, string> settings, IRewriteContext rewriteContext) {}

        public string Rewrite(string value)
        {
            return RewriteEngine.RewriteUri(value, ActionType.Rewrite);
        }
    }

    public class RewriteProvider : BaseProvider
    {
        protected override ActionType Action
        {
            get { return ActionType.Rewrite; }
        }
    }

    public class RedirectTemporaryProvider : BaseProvider
    {
        protected override ActionType Action
        {
            get { return ActionType.RedirectTemporary; }
        }
    }

    public class RedirectPermanentProvider : BaseProvider
    {
        protected override ActionType Action
        {
            get { return ActionType.RedirectPermanent; }
        }
    }

    public static class RewriteEngine
    {
        public static string RewriteUri(string uri, ActionType action)
        {
            // Not actual rule engine implementation... just to demonstrate what it would do.
            var ub = new UriBuilder(uri);

            // Simulate a match on a rewrite rule
            if (string.Equals(ub.Host, "rewrite.com", StringComparison.InvariantCultureIgnoreCase) && action == ActionType.Rewrite)
            {
                ub.Host = "rewrite-rewritten.com";
                return ub.ToString();
            }

            // Simulate a match on a temporary redirect rule
            if (string.Equals(ub.Host, "redirect.com", StringComparison.InvariantCultureIgnoreCase) && action == ActionType.RedirectTemporary)
            {
                ub.Host = "redirect-rewritten.com";
                return ub.ToString();
            }

            // No rules matched. This will make the condition in the rewrite rule not match.
            return string.Empty;
        }
    }
}

Apart from that, you need to setup IIS to allow the custom server variable and you need to sign and register the providers in the gac... This seems just a little more complicated than simply to be able to manipulate the request url like you are from the native api.


Solution

  • I've ended up using the ManagedFusion rewriter. The main reason is that this module seems fairly extensible and it also sports a built-in proxy. Whether it performs or not, only time, and a couple of tests, will tell.