Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles. Follow galratner on Twitter
Eliminating the duplicate String Connection in a Custom Rewrite Provider for URL Rewrite Module

I was reading a tutorial from Ruslan Yakushev titled Developing a Custom Rewrite Provider for URL Rewrite Module on iis.net the other day and decided to give it a try.

I downloaded the samples and rewrote the DB Provider with my own logic. The result was good, but, the module uses its one configuration section.

It’s great if you have access to the IIS Manager and can quickly pick up keys and assign them values, however, you can end up with duplicate application keys or connection strings.

The module is relaying on an implementation of IProviderDescriptor to expose its settings to IIS Manager. Since a DB Provider needs a connection to a database, the sample module, which uses its own connection string in the provider settings section, creates a duplicate, and unnecessary connection string.

The application’s connection string was in a file called connectionstrings.config referenced by a configsource in the connectionStrings section like so:

<connectionstrings configsource="connectionStrings.config"></connectionstrings>

I needed a way to access this value from the module in order to use the connection string I got. I tried ConfigurationManager.ConnectionStrings["StringConnection"].ConnectionString, but, this only returned the connection string in the machine.config. then I turned to serverManager.GetWebConfiguration and WebConfigurationManager.OpenWebConfiguration both proved to be ineffective.

Although the module is loaded to the worker process of the application pool, I encountered security errors and the module simply couldn’t use the main settings in web.config.

The solution came from Richard Marr, an escalation engineer on the IIS support team at Microsoft. Richard is a true IIS guru and he suggested a combination of direct fso access to the file containing the connection string along with passing the module the relative path of the web application by use of the tag {DOCUMENT_ROOT} in the module’s settings section. This tag was developed for the file rewrite provider and proved to be useful in this case. Pointing the module to the connection string file looked like:

<add key="StringConnectionPath" value="{DOCUMENT_ROOT}\connectionStrings.config" />

From there on it was just a matter of parsing the XML . Here in the complete example

 
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Microsoft.Web.Iis.Rewrite;

public sealed class SqlDBRewriteProvider : IRewriteProvider, IDisposable
{
    private Timer timer;
    private IRewriteContext rewriteContext;
    IDictionary<stringstring> settings;
    string connectionString = string.Empty;
    private string storedProcedure = string.Empty;

    public void Initialize(IDictionary<stringstring> settings, IRewriteContext rewriteContext)
    {
        this.rewriteContext = rewriteContext;
        this.settings = settings;
        string stringConnectionPath = string.Empty;
        string stringConnectionName = string.Empty;
        // Get the connection string from the name and path
        if (settings.TryGetValue("StringConnectionPath"out stringConnectionPath) && settings.TryGetValue("StringConnectionName"out stringConnectionName))
        {
            XDocument doc = XDocument.Load(stringConnectionPath);
            if (doc == null)
                throw new Exception("cannot load string connection file");
            var connectionStringElement = doc.Descendants("add").Where(e => e.Attribute("name").Value == stringConnectionName).FirstOrDefault();
            if (connectionStringElement == null)
                throw new Exception("connection string not found");
            connectionString = connectionStringElement.Attribute("connectionString").Value;
        }
        // Get the sproc
        settings.TryGetValue("StoredProcedure"out storedProcedure);
        // CacheMinutesInterval is an optional parameter.
        string minutesString;
        if (settings.TryGetValue("CacheMinutesInterval"out minutesString))
        {
            int minutes = 0;
            if (!string.IsNullOrEmpty(minutesString) && int.TryParse(minutesString, out minutes) && minutes > 0)
            {
                int period = minutes * 60 * 1000;
                this.timer = new Timer(TimerCallback, null, period, period);
            }
        }
    }
    [SuppressMessage("Microsoft.Security""CA2100:Review SQL queries for security vulnerabilities")]
    public string Rewrite(string value)
    {
        using (SqlConnection connection = new SqlConnection(this.connectionString))
        {
            using (SqlCommand command = connection.CreateCommand())
            {
                command.Parameters.Add("@Input"SqlDbType.NVarChar).Value = value;
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = this.storedProcedure;
                connection.Open();
                return (string)command.ExecuteScalar();
            }
        }
    }
    private void TimerCallback(object state)
    {
        if (this.rewriteContext != null)
        {
            this.rewriteContext.ClearRewriteCache();
        }
    }
    public void Dispose()
    {
        if (this.timer != null)
        {
            this.timer.Dispose();
            this.timer = null;
        }
        this.rewriteContext = null;
        GC.SuppressFinalize(this);
    }
}

This the Module's config section:

<system.webServer>
    <rewrite>
      <providers>
        <provider name="DB" type="SqlDBRewriteProvider, IISRewrite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cbbd7bb4d6722457">
          <settings>
            <add key="StringConnectionPath" value="{DOCUMENT_ROOT}\connectionStrings.config" />
            <add key="StringConnectionName" value="StringConnection" />
            <add key="StoredProcedure" value="GetRoute" />
            <add key="CacheMinutesInterval" value="10" />
          </settings>
        </provider>
      </providers>
      <rules>
        <rule name="RewriteUserFriendlyURL1" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{DB:{R:1}}" pattern="(.+)" />
          </conditions>
          <action type="Rewrite" url="{C:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>

Big thank you to Richard. You can find his excellent blog here: http://blogs.iis.net/richma

Shout itkick it on DotNetKicks.com


Posted 19 Jun 2010 4:04 AM by Gal Ratner
Filed under: ,

Powered by Community Server (Non-Commercial Edition), by Telligent Systems