Search This Blog

Friday, January 14, 2011

Hydrating entities for a custom DAL

There seems to be an endless debate on which data layer tools to use, and not to disappoint, I have some of my own opinions. 

I tend to stay away from ORM's because they are usually a bit heavy handed.  One of my favorite auto generated datalayers is .NetTiers.  It's a CodeSmith template that generates a whole data layer for you.  The downside to this is a lot of code and SP's.

My current employer is very concerned about scalability and performance, so we opt more for handwritten code over generated code.  As a rule, every stored proc, even simple CRUD, is written by a dedicated DB developer.  Some might say it's overkill, but I like how it's working so far.

So, with that in mind, one of the most tedious aspects of creating a DAL is creating the entities and writing the code to hydrate them.

LINQ to SQL is nice for this, but if you're still using ADO.Net this may be useful for you.

Here's a little snippit of code I've been using for a few years now to get data out of a datareader and into a new instance of an entity:

Here's a sample entity:
public class Customer

{
  private int _id;
  private int _username;


  [Column(IsPrimaryKey = true, Storage = "id")]
  public int Id
  {
    get { return _id; }
    set { _id = value; }
  }

  [Column(Storage = "Customer_Username")]
  public int Username
  {
    get { return _username; }
    set { _username = value; }
  }
}


One thing you may notice above is the Column attribute. To keep this example simple, I just used the System.Data.Linq.Mapping.ColumnAttribute class. In the past I created custom attributes, but this post isn't about how to create custom attributes, it's about DAL's. So I'm simply using this attribute to keep track of the column name that the property maps to. It's not necessary, as you'll see further down, but without it your property names must match your column names in your resultset. In this example, the resultset will have 2 columns (id, Customer_username), but I have effectively mapped that to a Customer with properties (Id, Username). Note that it is case sensitive so the mapping for Id was necessary.

Here's my entityhydrator method:

public static class EntityHydrator
{
  public static T HydrateEntity<T>(IDataRecord dataRow)
  {
    Type type = typeof(T);

    CacheProvider cache = CacheProvider.GetInstance();

    ConstructorInfo constructorInfo;
    PropertyInfo[] properties;

    if (!cache.Contains(type.Name + "_constructor"))
    {
      cache.Add(type.Name + "_constructor", type.GetConstructor(Type.EmptyTypes));

    }
    constructorInfo = (ConstructorInfo)cache[type.Name + "_constructor"];

    if(!cache.Contains(type.Name+"_propertyInfo"))
    {
      cache.Add(type.Name + "_propertyInfo", type.GetProperties());

    }
    properties = (PropertyInfo[])cache[type.Name + "_propertyInfo"];

    var instance = constructorInfo.Invoke(null);
    foreach (var propertyInfo in properties)
    {

      string columnName;
      if (!cache.Contains(type.Name + "_"+propertyInfo.Name+"_columnattribute"))
      {
        columnName = propertyInfo.Name;
        var attributeArray = propertyInfo.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute), true);
        if (attributeArray.Count() > 0)
        {
          columnName = ((System.Data.Linq.Mapping.DataAttribute)attributeArray[0]).Storage;
        }
        cache.Add(type.Name + "_"+propertyInfo.Name+"_columnattribute",columnName);
      }
      columnName = (string) cache[type.Name + "_"+propertyInfo.Name+"_columnattribute"];


      try
      {
        propertyInfo.SetValue(instance, dataRow[columnName], null);
      }
      catch { }
    }

    return (T)instance;
  }
}


In short, what this is doing is looking at the type T, getting a constructor for T using reflection, instanciating T, getting each property in the instance, and setting each property to be the value of the datafield in the current row of the datareader that matches by name (or Column attribute if present).
There are some key things to note here:
  • The method is generic, simply specify the type when you call it and it should work
  • The cache object is storing the ConstructorInfo, PropertyInfo, and attributes. Reflection is slow. This is important if you want it to be fast
  • Note how I look for the ColumnAttribute attached to each property. It's not necessary because if not found, it defaults to using property name as the field name when looking in the recordset for the data
  • There is a potential for many exceptions to be happening here. For brevity, I left out the code for dealing with this, but for real world code where speed is a concern, exception handling is slow. Handling them is not enough, you must do something to prevent the exceptions from happening. More on that below.

Something else to note here is that this method is very basic. I didn't want to type a book here, so I left out many other considerations. For example, if a column is an integer and is nullable, but the entity's property is declared as int instead of int?. Additionally, certain datatypes, i.e. enums, require a bit of extra care.

As mentioned above, something else to consider would be the empty catch. It simply does nothing, the property's value is set to the default for it's type. It's there to handle the case where a column doesn't map to a property (i.e. IsDirty) or there is a problem casting the data. In the past, I've had multiple complex catch blocks to determine what is wrong and react appropriately.
Usually at a minimum would be a catch for IndexOutOfRangeException and I would handle this by removing that property from the cached properties to prevent the exception from firing again. Enums (where the data is the name of the enum value), nullable (bit, int, etc.) columns and others are all potential problems as well.
Play with the code, you'll have a robust method in no time.

Finally, on to usage:

.
.
.
var datareader = cmd.ExecuteReader();

List customers = new List<customer\>();
while (datareader.Read())
{
  var customer = EntityHydrator.HydrateEntity<customer>(datareader);
  customers.Add(customer);
}


This snippet of code assumes we have already prepared a sqlcommand to get a resultset for several of our entities. From there, were going to instanciate a new list of our entity and loop through the resultset, hydrating new instances of our entity as we go and adding them to the list.

Like I said above, I've been using this for a few years, and it's been a great way to save my fingers from all that extra typing you have to do when hydrating entities. Something else to look at is using Linq and lambdas to do the same.

Wednesday, December 8, 2010

Catching up

I was afraid that I would neglect this blog when I started it, and it appears I have done just that.  What can I say, I've been busy.  2 kid, changing jobs, multiple home improvement projects....  I'm back now, and just wanted to post some of what I've been doing.

The new job is going great by the way.  I'm still doing .NET development, but nowhere near the amount of SQL I did previously.  We have dedicated DB developers who handle all of the data modeling and stored proc writing.  I do miss it, but I'm happy to be able to devote more time to learning more about .NET.  Aside from that, I've been very pleased with how it's going.  My peers are talented and bring diverse skills with them, so more opportunity for me to learn from them.

Last week, they even put all of the web app team into training for MVC2.  I have nothing but the best opinion of Brock Allen and DevelopMentor.  I've taken about 6 training courses in the past that were geared towards developers, and all of them were lacking.  Either the instructor only knew the bare minimum to get you past a snag doing the excercises, or if they knew more, they were unwilling to share, citing scope of the course doesn't cover the question.  Brock, was very different.  Not only did he know the course material inside and out, he was able to answer questions about behaviors of the framework by citing the class and using reflector to back up his statements.  Additionally, he took the time to learn the source code of MVC2 and was able to walk us through pieces of the implimentation he felt we should know about or may want to extend.

The training also covered a couple other concepts I've been looking at for a while, dependency injection, inversion of control, unit testing and test driven design.  For about 2 years, off and on, I've been struggling with trying to consistently impliment unit testing.  Again, I was impressed with Brock, not only was he able to cite specific, concrete examples of how he solved the issues i've struggled with in the past, but he even pointed me to several blog posts that went further into detail with my exact issues.  Jason Diamond's blog was one of them.  He also showed us Osherove's blog, and recommended we do his TDD Kata frequently.

I'll be working on my Kata, and will let you know how it goes.

Saturday, March 6, 2010

More Configuration Protected Data Encryption

I'm back again with a new enhancement to my configuration encryption approach.  This time I wanted to tackle the ability to encrypt the values in the <appsettings> block but still have all the key names readable.

Since this ties to the connection strings, I decided a little refactoring was in order.  I created a base class to contain all the shared functionality:


using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;

namespace ProtectedConfiguration
{
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public abstract class ProtectedConfigurationProviderBase : ProtectedConfigurationProvider
{
#region members

// Provider name
private string pName;

// Create Entropy To salt the process aka 'Magic Salt'
private readonly byte[] entropy = Encoding.Unicode.GetBytes("magicsalt");

#endregion






#region Overrides of ProviderBase

//
// ProviderBase.Name
//
public override string Name
{
get { return pName; }
}

#endregion




#region Overrides of ProtectedConfigurationProvider

//
// ProviderBase.Initialize
//
public override void Initialize(string name, NameValueCollection config)
{
pName = name;
base.Initialize(name, config);
}

#endregion



#region Methods

/// <summary>
/// Encrypt the passed string
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected string EncryptData(string data)
{
//convert to byte array
byte[] valBytes = Encoding.ASCII.GetBytes(data);


// Use DPAPI to Encrypt
byte[] encryptedData = ProtectedData.Protect
(valBytes,
entropy, DataProtectionScope.LocalMachine);

//convert to base64 and wrap with xml tags
return Convert.ToBase64String(encryptedData);
}


/// <summary>
/// Decrypt the passed string
/// </summary>
/// <param name="encryptedData"></param>
/// <returns></returns>
protected string DecryptData(string encryptedData)
{
string password = encryptedData;

//convert to encrypted byte array
byte[] valBytes = Convert.FromBase64String(password);


// Decrypt using DPAPI
byte[] decryptedData = ProtectedData.Unprotect
(valBytes, entropy, DataProtectionScope.LocalMachine);

var encoding = new ASCIIEncoding();

//convert to ascii
return encoding.GetString(decryptedData);
}

#endregion
}
}


I also made some changes to my ConnectionStringProtectedConfigurationProvider.  I made it db agnostic, so any connection string, SqlServer, Oracle, DB2, etc. will work as long as it has the word "Password" in it.  Of course I also changed it to inherit my base class above:

using System;
using System.Data.Common;
using System.Security.Permissions;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Specialized;
using System.Configuration;


namespace ProtectedConfiguration
{
public sealed class ConnectionStringProtectedConfigurationProvider : ProtectedConfigurationProviderBase
{
#region Overrides of ProtectedConfigurationProvider

/// <summary>
/// Encrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing encrypted data.
/// </returns>
/// <param name="node">The <see cref="T:System.Xml.XmlNode" /> object to encrypt.</param>
public override XmlNode Encrypt(XmlNode node)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;

XmlNodeList nodeList = node.SelectNodes("/connectionStrings/add");
if (nodeList != null)
{
for (int i = 0; i < nodeList.Count; i++)
{
XmlAttribute attribute = nodeList[i].Attributes["connectionString"];
DbConnectionStringBuilder builder = new DbConnectionStringBuilder
{ConnectionString = attribute.Value};
if (builder.ContainsKey("Password") && !string.IsNullOrEmpty(builder["Password"].ToString()))
{
builder["Password"] = EncryptData(builder["Password"].ToString());
attribute.Value = builder.ToString();
}
}
}
doc.LoadXml("<EncryptedData>" + node.InnerXml +"</EncryptedData>");

return doc.DocumentElement;
}

/// <summary>
/// Decrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing decrypted data.
/// </returns>
/// <param name="encryptedNode">The <see cref="T:System.Xml.XmlNode" /> object to decrypt.</param>
public override XmlNode Decrypt(XmlNode encryptedNode)
{
//get each <add> in the connection string block
XmlNodeList nodeList = encryptedNode.SelectNodes("/EncryptedData/add");

//start building the output xml string
StringBuilder output = new StringBuilder();
output.Append("<connectionStrings>");

if (nodeList != null)
{
for (int i = 0; i < nodeList.Count; i++)
{
//get the text for the connection string
XmlAttribute attribute = nodeList[i].Attributes["connectionString"];

//use DbConnectionStringBuilder to parse
DbConnectionStringBuilder builder = new DbConnectionStringBuilder
{ConnectionString = attribute.Value};
if (builder.ContainsKey("Password") && !string.IsNullOrEmpty(builder["Password"].ToString()))
{
//decrypt it
builder["Password"] = DecryptData(builder["Password"].ToString());
attribute.Value = builder.ToString();
}
//add the connection string with decrypted password to output
output.Append(nodeList[i].OuterXml);
}
}
//add closing tag
output.Append("</connectionStrings>");

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;

xmlDoc.LoadXml(output.ToString());

return xmlDoc.DocumentElement;
}

#endregion
}
}


And on to the AppSettings, nothing too fancy, at least after you've seen the above it's really just more of the same:


using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Security.Permissions;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Specialized;
using System.Configuration;

namespace ProtectedConfiguration
{
class AppSettingsProtectedConfigurationProvider : ProtectedConfigurationProviderBase
{
#region members

private List<string> keys = new List<string>();

#endregion



#region Overrides of ProtectedConfigurationProvider

/// <summary>
/// Stores the list of keys that need encrypted
/// </summary>
/// <param name="name"></param>
/// <param name="config"></param>
public override void Initialize(string name, NameValueCollection config)
{
keys = new List<string>(config["keys"].Split(','));
base.Initialize(name, config);
}

/// <summary>
/// Encrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing encrypted data.
/// </returns>
/// <param name="node">The <see cref="T:System.Xml.XmlNode" /> object to encrypt.</param>
public override XmlNode Encrypt(XmlNode node)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;

XmlNodeList nodeList = node.SelectNodes("/appSettings/add");
foreach (XmlNode keyvaluepair in nodeList)
{
XmlAttribute key = keyvaluepair.Attributes["key"];
XmlAttribute value = keyvaluepair.Attributes["value"];

if (keys.Contains(key.Value))
{
value.Value = EncryptData(value.Value);

}
}
doc.LoadXml("<EncryptedData>" + node.InnerXml + "</EncryptedData>");

return doc.DocumentElement;
}

/// <summary>
/// Decrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing decrypted data.
/// </returns>
/// <param name="encryptedNode">The <see cref="T:System.Xml.XmlNode" /> object to decrypt.</param>
public override XmlNode Decrypt(XmlNode encryptedNode)
{
//start building the output xml string
StringBuilder output = new StringBuilder();
output.Append("<appSettings>");

//get each <add> in the appSettings block
XmlNodeList nodeList = encryptedNode.SelectNodes("/EncryptedData/add");

foreach (XmlNode keyvaluepair in nodeList)
{
XmlAttribute key = keyvaluepair.Attributes["key"];
XmlAttribute value = keyvaluepair.Attributes["value"];

if (keys.Contains(key.Value))
{
value.Value = DecryptData(value.Value);

}
output.Append(keyvaluepair.OuterXml);
}
output.Append("</appSettings>");

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;

xmlDoc.LoadXml(output.ToString());

return xmlDoc.DocumentElement;
}

#endregion
}
}


You also have to modify the web.config to tell it what assembly to use for the encryption and decryption. There are 2 entries here. One for connection strings, and one for appSettings. Notice in the AppSettingsProtectedConfigurationProvider there is an attribute named keys. It lists the keys in the appSettings, by name, in a comma seperated list that should be encrypted.

<configProtectedData>
<providers>
<add name="ConnectionStringProtectedConfigurationProvider" type="ProtectedConfiguration.ConnectionStringProtectedConfigurationProvider,ProtectedConfiguration"/>
<add name="AppSettingsProtectedConfigurationProvider" type="ProtectedConfiguration.AppSettingsProtectedConfigurationProvider,ProtectedConfiguration" keys="testkey1,testkey2"/>
</providers>
</configProtectedData>

And last, but not least, to do the encryption, you will need to generate a strong name key, and give your assembly a strong name. Once that is done, you can add it to the GAC and use aspnet_regiis.exe to encrypt your web config.
There is an alternative, do it with code. I added this to the global.asax.cs:


protected void Application_Start(object sender, EventArgs e)
{
/**************************************
* Verify connection string is encrypted
* ***********************************/

//get the application path so we can open the config file for writing
string path = HttpContext.Current.Request.ApplicationPath;
//open the config file
Configuration config = WebConfigurationManager.OpenWebConfiguration(path);
//get the connection string section
ConfigurationSection configurationSection;

configurationSection = config.GetSection("connectionStrings");
//If it's not encrypted, encrypt it.
if (configurationSection != null && !configurationSection.SectionInformation.IsProtected)
{
configurationSection.SectionInformation.ProtectSection("ConnectionStringProtectedConfigurationProvider");
configurationSection.SectionInformation.ForceSave = true;

config.Save(ConfigurationSaveMode.Full);

}

configurationSection = config.GetSection("appSettings");
//If it's not encrypted, encrypt it.
if (configurationSection != null && !configurationSection.SectionInformation.IsProtected)
{
configurationSection.SectionInformation.ProtectSection("AppSettingsProtectedConfigurationProvider");
configurationSection.SectionInformation.ForceSave = true;

config.Save(ConfigurationSaveMode.Full);
}


}

Monday, March 1, 2010

Encrypting Passwords in a Connection String

One of the topics that has come up repeatedly is encrypting connection strings.

Many times, a team doesn't want to encrypt the whole connection string.  It makes it easier for support, especially if it's a larger team that's on a support rotation.  If you have multiple environments, it's easier to determine which database your app is connected to.  If you have granular DB security, it's easier to trouble shoot what roles are missing from which account if you know what the account is.

Some people argue that you shouldn't have a password in a connection string, that you should be using integrated security.  They seem to forget that there are situations where that's not possible.  For example, if your web servers are in a DMZ, they typically don't have access to Active Directory.

Do a Google search, and you'll see how many people just want to protect the password, but are told it's just not possible.  That's not true, and I'm about to show you how you can encrypt just a password in a connection string.


For more on this, look here:
Donald Frederick's Blog: More Configuration Protected Data Encryption



Typically, what you see is this:
<connectionStrings >
            <add name="PortalConnectionString" connectionString="Server=portalserver\sql2005;Database=PortalDb;User ID=PortalUser;Password=portalPass" />
<connectionStrings>

But in my case, I wanted to see this:
<connectionStrings configProtectionProvider="ConnectionStringProtectedConfigurationProvider">
            <EncryptedData>
                        <add name="PortalConnectionString" connectionString="Data Source=portalServer\sql2005;Initial Catalog=PortalDb;User ID=PortalUser;Password="AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA9UDF9oj2/0ePz/g7oTJgQAAAACAAAAAAADZgAAqAAAABAAAADAto3n3xCpElzVztkGiUfHAAAAAASAAACgAAAAEAAAACCXh2gjmKZfsnS9MRdoBqwIAAAAAb6Uu4W7Vk4UAAAACJlUP0qazHy6TieOBZXD4EfhfzA="" />
            <EncryptedData>
<connectionStrings>

Here's a good primer to get you familiar with the DPAPI and encrypting configuration sections found on MSDN.

I'm not going to explain everything here, I'm going to assume you have a working knowledge of encryption, C#, and .NET.  I also would like to warn you against blindly copying and pasting this.  It'll work, but only for SqlConnectionStrings, and you won't be able to copy and paste encrypted information from machine to machine.

Here's my provider class:

using System;
using System.Data.SqlClient;
using System.Security.Permissions;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Specialized;
using System.Configuration;
namespace ProtectedConfiguration
{
    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public sealed class ConnectionStringProtectedConfigurationProvider : ProtectedConfigurationProvider
    {
        // Create Entropy To salt the process aka 'Magic Salt'
        readonly byte[] entropy = Encoding.Unicode.GetBytes("magicsalt");
        private string providerName;
        #region Overrides of ProviderBase
        //
        // ProviderBase.Name
        //
        public override string Name
        {
            get { return providerName; }
        }
      
        #endregion
        #region Overrides of ProtectedConfigurationProvider
        //
        // ProviderBase.Initialize
        //
        public override void Initialize(string name, NameValueCollection config)
        {
            providerName = name;
            base.Initialize(name, config);
        }
        ///
        /// Encrypts the passed object from a configuration file.
        ///
        ///
        /// The object containing encrypted data.
        ///
        /// The object to encrypt.
        public override XmlNode Encrypt(XmlNode node)
        {
            XmlDocument doc = new XmlDocument();
            doc.PreserveWhitespace = true;
           
            XmlNodeList nodeList = node.SelectNodes("/connectionStrings/add");
            if (nodeList != null)
            {
                for (int i = 0; i < nodeList.Count; i++)
                {
                    XmlAttribute attribute = nodeList[i].Attributes["connectionString"];
                    SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(attribute.Value);
                    if (!string.IsNullOrEmpty(builder.Password))
                    {
                        builder.Password = EncryptData(builder.Password);
                        attribute.Value = builder.ToString();
                    }
                }
            }
            doc.LoadXml("<EncryptedData>" + node.InnerXml +"</EncryptedData>"); 

            return doc.DocumentElement;
           
        }
        ///
        /// Decrypts the passed object from a configuration file.
        ///
        ///
        /// The object containing decrypted data.
        ///
        /// The object to decrypt.
        public override XmlNode Decrypt(XmlNode encryptedNode)
        {
            //get each in the connection string block
            XmlNodeList nodeList = encryptedNode.SelectNodes("/EncryptedData/add");
           
            //start building the output xml string
            StringBuilder output = new StringBuilder();
            output.Append("<connectionStrings>");
            if (nodeList != null)
            {
                for (int i = 0; i < nodeList.Count; i++)
                {
                    //get the text for the connection string
                    XmlAttribute attribute = nodeList[i].Attributes["connectionString"];
                    //use sqlconnectionstringbuilder to parse
                    SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(attribute.Value);
                    if (!string.IsNullOrEmpty(builder.Password))
                    {
                        //decrypt it
                        builder.Password = DecryptData(builder.Password);
                        attribute.Value = builder.ToString();
                    }
                    //add the connection string with decrypted password to output
                    output.Append(nodeList[i].OuterXml);
                }
            }
            //add closing tag
            output.Append("</connectionStrings>");
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
           
            xmlDoc.LoadXml(output.ToString());
            return xmlDoc.DocumentElement;
           
        }
        private string EncryptData(string data)
        {
            //convert to byte array
            byte[] valBytes = Encoding.ASCII.GetBytes(data);
            // Use DPAPI to Encrypt
            byte[] encryptedData = ProtectedData.Protect
                (valBytes,
                 entropy, DataProtectionScope.LocalMachine);
            //convert to base64 and wrap with xml tags
            return  Convert.ToBase64String(encryptedData) ;
        }
        private string DecryptData(string encryptedData)
        {
            //convert to encrypted byte array
            byte[] valBytes = Convert.FromBase64String(encryptedData);
            // Decrypt using DPAPI
            byte[] decryptedData = ProtectedData.Unprotect
                (valBytes, entropy, DataProtectionScope.LocalMachine);
            var encoding = new ASCIIEncoding();
            //convert to ascii
            return encoding.GetString(decryptedData);
        }
     
        #endregion
    }
}


OK, got that?
The main thing is we extended ProtectedConfigurationProvider.  We overrode the initialize to store the provider name and we overrode the Encrypt and Decrypt methods so we could parse the xml in the   section.  If you inspect the EncryptData method, you will see we're just using the ProtectedData class to do the encryption.

To use it you need to add it to your web.config as a child of
<configProtectedData>
            <providers>
                  <add name="ConnectionStringProtectedConfigurationProvider" type="ProtectedConfiguration.ConnectionStringProtectedConfigurationProvider,ProtectedConfiguration"/>
            <providers>
<configProtectedData>


To actually do the encryption, you've got a couple options.  
  • You could encrypt it when you deploy using reg_iis.exe, to do that you have to give the assembly a strong name and put it into the GAC. 
  • Or you could call it programatically when your application starts
I decided to go with the second option, so I had to add a little more code.  In the Global.asax, I added this:

protected void Application_Start(object sender, EventArgs e)
        {
            /**************************************
             * Verify connection string is encrypted
             * ***********************************/
            //get the application path so we can open the config file for writing
            string path = HttpContext.Current.Request.ApplicationPath;
            //open the config file
            Configuration config = WebConfigurationManager.OpenWebConfiguration(path);
            //get the connection string section
            ConfigurationSection configurationSection = config.GetSection("connectionStrings");
            //If it's not encrypted, encrypt it.
            if(configurationSection != null && !configurationSection.SectionInformation.IsProtected)
            {
                configurationSection.SectionInformation.ProtectSection("ConnectionStringProtectedConfigurationProvider");
                configurationSection.SectionInformation.ForceSave = true;
                config.Save(ConfigurationSaveMode.Full);
            }
           
        }

Again, I caution you against copying and pasting.  This is just meant to show the concepts, it's not a robust solution.  It will encrypt on application startup, but if you change the web.config on the fly, it won't.  I suggest a slightly more robust implementation, but that's outside the scope of this discussion. 


I hope you found this useful.  More to come in the future!


Friday, February 26, 2010

First Post

Hello,

I finally decided to create a blog. I don't normally have a lot to say, but from time to time I do. Mostly, you can expect rants, but on occasion I might post something informative.

I'm currently a .Net Web Developer, and sometimes I'll get a good idea, or write some code that I think the rest of the world would appreciate. That's really the reason why I wanted to start this blog.

Thanks for visiting,

Donald