Thursday, June 05, 2008

C# Anonymous Delegate Pitfall

What is output of following code?
using System;
using System.Collections.Generic;
using System.Text;

namespace AnonymousDelegate
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("AnonymousDelegate1:");
AnonymousDelegate1();
Console.WriteLine();

Console.WriteLine("AnonymousDelegate2:");
AnonymousDelegate2();
Console.WriteLine();

Console.WriteLine("AnonymousDelegate3:");
AnonymousDelegate3();
Console.Read();
}

static void AnonymousDelegate1()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate() { Console.WriteLine(i); });
}
foreach (Action action in actions)
{
action();
}
}

static void AnonymousDelegate2()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int temp = i;
actions.Add(delegate() { Console.WriteLine(temp); });
}
foreach (Action action in actions)
{
action();
}
}

static void AnonymousDelegate3()
{
List<Action> actions = new List<Action>();
int temp;
for (int i = 0; i < 3; i++)
{
temp = i;
actions.Add(delegate() { Console.WriteLine(temp); });
}
foreach (Action action in actions)
{
action();
}
}
}
}
The result may surprise you:
AnonymousDelegate1:
3
3
3

AnonymousDelegate2:
0
1
2

AnonymousDelegate3:
2
2
2
Why is inconsistent for each test? That stems from the way how .NET handle anonymous delegate or anonymous method.

.NET compiler will create a delegate and a static method for an anonymous delegate, just like a traditional delegate. There's no anonymous concept in intermediate language (IL) in any .NET assembly.

The thing becomes more interesting when a variable used inside anonymous delegate/method is declared outside the anonymous delegate/method, which is called closure scenario. .NET compiler will wrap the anonymous method and the variable to a sealed class: anonymous method becomes a class method, variable becomes a public member. We can examine this by looking inside the IL code (click to see bigger picture):



As we can see there're 3 inner classes (<>c__DisplayClass2/5/9 in red boxes) generated for 3 anonymous methods. All three classes have the same structure:
private sealed class ComiplerGeneratedClass
{
public int i;
public void ActionMethod()
{
Console.WriteLine(this.i);
}
}
That makes sense but why having different results? Let's check the IL code for AnonymousDelegate1:



We can see that only one AnonymousClass object is created, and AnonymousClass.i is used for looping condition (for loop) check.

But when we look at the IL code for method of AnonymousDelegate2, we found AnonymousClass is created 3 times inside the for loop. To be more readable, we translate the IL code back to C# code for all three test methods:
      private sealed class AnonymousClass // Generated by compiler
{
public int i;
public void ActionMethod()
{
Console.WriteLine(this.i);
}
}

static void AnonymousDelegate1()
{
AnonymousClass anonymousClass = new AnonymousClass();
List<Action> actions = new List<Action>();
anonymousClass.i = 0;
for (; anonymousClass.i < 3; anonymousClass.i++)
{
Action action = new Action(anonymousClass.ActionMethod);
actions.Add(action);
}
foreach (Action action in actions)
{
action.Invoke();
}
}

static void AnonymousDelegate2()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
AnonymousClass anonymousClass = new AnonymousClass();
anonymousClass.i = i;
Action action = new Action(anonymousClass.ActionMethod);
actions.Add(action);
}
foreach (Action action in actions)
{
action.Invoke();
}
}

static void AnonymousDelegate3()
{
AnonymousClass anonymousClass = new AnonymousClass();
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
anonymousClass.i = i;
Action action = new Action(anonymousClass.ActionMethod);
actions.Add(action);
}
foreach (Action action in actions)
{
action.Invoke();
}
}
Now the answer is clear. .NET compiler generates different code depending on the scale of closure variable. It looks very confusing at the beginning and it's easy to write buggy code if we don't understand this.

We have to put it in mind that a real class is created, and a public member is used to store a closure variable for anonymous methods with closure. If not sure how the closure variable is handled, use minimum scale of local variable and pass it to the anonymous method, like what AnonymousDelegate2 does; that ensures each anonymous method using independent variable, but be aware of consuming more resource in such case because more anonymous classes are generated at run time.

Tuesday, March 25, 2008

Remove Files Read-only Attribute In Windows

A Unix machine that's not in our control is running a daily process of copying files in folders to a Windows server under our control by rsync. But some of the files have read-only permission in the target Windows machine, and these read-only files in Windows cause some problem in a separate file-handling process.

To remove a file read-only permission in Windows we can use attrib command:
attrib -r filename
About DOS command will remove all attributes from the file. But attrib can only handle single file. In Unix/Linux we can simply call "chomd -R 777 folder" to recursively change the permission of all files in the folder, but not that easy in Windows.

To do this unix-simple-task in Windows, I created a console application:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.IO;
using System.Security.AccessControl;

namespace Bell.Sympatico.Utilities.ReadOnlyRemover
{
/// <summary>
/// This console application is to remove file's read only attribute
/// The user who runs the application needs the modify permission for the files
/// Application configuration file name: ReadOnlyRemover.exe.config
///
/// Usage:
/// 1. Process all files in a folder and its subfolders.
/// Specify the folder (RootFolder entry) in App.config configuration file
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all
/// 2. Process feed and/or log files.
/// Specify the file name for Feed files (FeedFile entry in configuration file) and/or
/// Specify the file name for Log files (LogFile entry in configuration file)
/// C:\ApplicationFolder>ReadOnlyRemover.exe
/// 3. Process all files in a folder and its subfolders. But the folder name is
/// provided in the arguments. The folder name can be a share folder in network.
/// You need the assign read/write permission for the network shared folder in
/// such case.
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all c:\test
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all \\NetworkShared\Feed1
///
/// A application log file will be created or appended during the process.
/// You can define the application log file name in configuration file (AppLogFile entry)
/// </summary>
class ReadOnlyRemover
{
static StreamWriter sw = null; // Write the logging in a file.
static bool doLog = true; // Do we need to do the logging during the processing?

/// <summary>
/// The entry point of the application
/// </summary>
/// <param name="args">Arguments</param>
static void Main(string[] args)
{
bool scanAll = false; // A flag if check all files inside a folder
if (args.Length > 0 && (args[0].ToLower() == "/all" || args[0].ToLower() == "all"))
{
scanAll = true;
}

string rootFolder = ConfigurationManager.AppSettings["RootFolder"];
if (string.IsNullOrEmpty(rootFolder) || !Directory.Exists(rootFolder))
{
rootFolder = "C:/Feed/"; // Default folder name
}
else if (!rootFolder.EndsWith("/") && !rootFolder.EndsWith("\\"))
{
rootFolder = rootFolder + "/";
}

if (scanAll && args.Length > 1)
{
string argument = args[1];
if (Directory.Exists(argument))
{
rootFolder = argument;
}
else
{
Console.WriteLine("Specified folder invalid!");
return;
}
}

string appLogFile = ConfigurationManager.AppSettings["AppLogFile"];
if (string.IsNullOrEmpty(appLogFile))
{
appLogFile = "ReadOnlyRemover.log.txt"; // Default application log name
}

try
{
sw = new StreamWriter(appLogFile, true);
sw.WriteLine("Process starts at " + DateTime.Now.ToString("yyyy/MM/dd HH:mm"));
sw.Flush();
}
catch (Exception ex)
{
doLog = false; // Do not write the log in the file in such case
Console.WriteLine("Could not create or open application log file. Error: " + ex.Message);
Console.WriteLine("Continue to process without logging.");
}

if (scanAll)
{
UpdateFolderRecursive(rootFolder);
}
else // Process the files that are listed in feedFile and/or logFile
{
bool hasFeedFile = true;
string feedFile = ConfigurationManager.AppSettings["FeedFile"];
if (string.IsNullOrEmpty(feedFile) || !File.Exists(feedFile))
{
Console.WriteLine("Miss Feed file!");
sw.WriteLine("Miss Feed file!");
hasFeedFile = false;
}
List<string> feedNameList = null;
if (hasFeedFile)
{
feedNameList = GetFileNames(feedFile, rootFolder);
UpdateFilesAttribute(feedNameList);
}

bool hasLogFile = true;
string logFile = ConfigurationManager.AppSettings["LogFile"];
if (string.IsNullOrEmpty(logFile) || !File.Exists(logFile))
{
Console.WriteLine("Miss Log file!");
sw.WriteLine("Miss LOg file!");
hasLogFile = false;
}
List<string> logNameList = null;
if (hasLogFile)
{
logNameList = GetFileNames(logFile, rootFolder);
UpdateFilesAttribute(logNameList);
}
}
if (doLog && sw != null)
{
sw.Close();
}
}

/// <summary>
/// Read a file storing all file names that need to be processed
/// Put all file names into a list
/// </summary>
/// <param name="fileName">File Name</param>
/// <param name="rootFolder">The root folder that holds the file</param>
private static List<string> GetFileNames(string fileName, string rootFolder)
{
List<string> fileNameList = new List<string>();
if (File.Exists(fileName))
{
try
{
using (StreamReader sr = new StreamReader(fileName))
{
while (sr.Peek() >= 0)
{
fileNameList.Add(rootFolder + sr.ReadLine());
}
}
}
catch (Exception ex)
{
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Error in reading file " + fileName + ": " + ex.Message;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
}
}
return fileNameList;

}

/// <summary>
/// Go throught all files and remove the read only attribute
/// </summary>
/// <param name="fileNames">File names</param>
private static void UpdateFilesAttribute(List<string> fileNames)
{
foreach (string file in fileNames)
{
if (File.Exists(file))
{
RemoveFileReadonlyAttribute(file);
}
else
{
/*
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Not a valid file: " + file;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
*/

}
}
}

/// <summary>
/// Recursively check all files in the folder and its subfolders
/// Remove read only for all files
/// </summary>
/// <param name="path">Folder Name</param>
private static void UpdateFolderRecursive(string path)
{
if (Directory.Exists(path))
{
string[] files = Directory.GetFileSystemEntries(path);
foreach (string entry in files)
{
if (Directory.Exists(entry))
{
// It's a folder, dig into it's subfolders
UpdateFolderRecursive(entry);
}
else if (File.Exists(entry))
{
// It's a file, check the attribute
RemoveFileReadonlyAttribute(entry);
}
}
}
}

/// <summary>
/// Check if the file is read only. if so remove the read only attribute.
/// </summary>
/// <param name="fileName">file name</param>
private static void RemoveFileReadonlyAttribute(string fileName)
{
if (!File.Exists(fileName))
return;

FileInfo fi = new FileInfo(fileName);
if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
{
// Remove the read only attribute
fi.Attributes -= FileAttributes.ReadOnly;
StringBuilder sb = new StringBuilder();
sb.Append(DateTime.Now.ToString("yyyy/MM/dd HH:mm"));
sb.Append(" - Read only attribute of file ");
sb.Append(fileName.Replace("\\", "/"));
sb.Append(" has been removed.");
if (doLog)
{
sw.WriteLine(sb.ToString());
sw.Flush();
}
else
{
Console.WriteLine(sb.ToString());
}
}
catch (Exception ex)
{
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Error in updating " + fileName + " attribute: " + ex.Message;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
}
}
}

/// <summary>
/// Add File Security for a file
/// AddFileSecurity(@"c:\Feed\abc.txt", "Everyone", FileSystemRights.FullControl, AccessControlType.Allow);
/// </summary>
/// <param name="filep">File Path</param>
/// <param name="account">Acount for the permission</param>
/// <param name="right">User right</param>
/// <param name="controlType">Allow or Deny</param>
public static void AddFileSecurity(string file, string account, FileSystemRights right, AccessControlType controlType)
{
FileSecurity fileSec = File.GetAccessControl(file);
fileSec.AddAccessRule(new FileSystemAccessRule(account, right, controlType));
File.SetAccessControl(file, fileSec);
}
}
}

Thursday, February 21, 2008

.NET Threading - AutoResetEvent And ManualResetEvent

In .NET EventWait handles are used for signaling for threads' communication. Signaling is when one thread waits until it receives notification from another. AutoResetEvent and ManualResetEvent are two EventWait handles available in .NET 2.0. AutoRsetEvent allows a thread to unblock once when it receives a signal from another and then it automatically resets. ManualResetEvent won't do reset by itself unless you do it manually. Following code sample demos such concept:
using System;
using System.Threading;

class Test
{
static void Main()
{
AutoResetEvent autoReset = new AutoResetEvent(false);
ManualResetEvent manualReset = new ManualResetEvent(false);
WaitHandle[] events = new WaitHandle[2] { autoReset, manualReset };

//Start 3 threads with AutoResetEvent
for (int i = 0; i < 3; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Run), autoReset);
}
Thread.Sleep(1000);
autoReset.Set();
Thread.Sleep(1000);
Console.WriteLine();

//Start 3 threads with ManualResetEvent
for (int i = 0; i < 3; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Run), manualReset);
}
Thread.Sleep(1000);
manualReset.Set();
Thread.Sleep(1000);

Console.WriteLine("\nMain thread completed...");
Console.ReadLine();
}

static void Run(object obj)
{
EventWaitHandle hander = obj as EventWaitHandle;
Console.WriteLine("Thread {0} is started and blocked...", Thread.CurrentThread.ManagedThreadId);
hander.WaitOne();
Console.WriteLine("Thread {0} is unblocked...", Thread.CurrentThread.ManagedThreadId);
}
}
Result:

Saturday, February 02, 2008

.NET 2.0 Configuration AppSettings In Custom Section

App.config or Web.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="MyCustomSection" type="CustomSettingManager, ConsoleApplication1"/>
</configSections>
<MyCustomSection>
<appSettings>
<add key="Key1" value="Value1"/>
<add key="Key2" value="Value2"/>
</appSettings>
</MyCustomSection>
</configuration>
Code behind:
using System;
using System.Configuration;

class Test
{
static void Main()
{
Console.WriteLine("Key:{0} Value:{1}", "key1", CustomSettingManager.GetCustomSetting("key1"));
Console.Read();
}
}

public class CustomSettingElement : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get { return this["key"] as string; }
}
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return this["value"] as string; }
}
}

public class CustomSettingCollection : ConfigurationElementCollection
{
public CustomSettingElement this[int index]
{
get
{
return base.BaseGet(index) as CustomSettingElement;
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}

protected override ConfigurationElement CreateNewElement()
{
return new CustomSettingElement();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomSettingElement)element).Key;
}
}

public class CustomSettingManager : ConfigurationSection
{
private const string sectionName = "MyCustomSection";
private const string collectionName = "appSettings";
private static CustomSettingManager configSection =
ConfigurationManager.GetSection(sectionName) as CustomSettingManager;

public static string GetCustomSetting(string key)
{
string value = string.Empty;
if (configSection != null && configSection[collectionName] != null)
{
CustomSettingCollection appSettings =
configSection[collectionName] as CustomSettingCollection;
foreach (CustomSettingElement item in appSettings)
{
if (string.Compare(item.Key, key, true) == 0)
{
value = item.Value;
}
}
}
return value;
}

[ConfigurationProperty(collectionName)]
public CustomSettingCollection AppSettings
{
get
{
return this[collectionName] as CustomSettingCollection;
}
}
}

Friday, January 18, 2008

Access Private Fields And Invoke Private Methods In .NET

using System;
using System.Reflection;

class Test
{
private int _number;
private int Number
{
get { return _number; }
set { _number = value; }
}

private int PrivateAdd(int a, int b)
{
return a + b;
}

private static int StaticAdd(int a, int b)
{
return a + b;
}

static void Main()
{
//Instance method
Test test = new Test();
MethodInfo privateMethod = test.GetType().GetMethod("PrivateAdd",
BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = new object[] { 1, 2 };
int result = (int)privateMethod.Invoke(test, parameters);

//Static method
MethodInfo staticMethod = typeof(Test).GetMethod("StaticAdd",
BindingFlags.NonPublic | BindingFlags.Static);
result = (int)staticMethod.Invoke(null, parameters);

//Private field
FieldInfo privateField = test.GetType().GetField("_number",
BindingFlags.NonPublic | BindingFlags.Instance);
privateField.SetValue(test, 123);
result = (int)privateField.GetValue(test);

//Private property
PropertyInfo privateProperty = test.GetType().GetProperty("Number",
BindingFlags.NonPublic | BindingFlags.Instance);
privateProperty.SetValue(test, 456, null);
result = (int)privateProperty.GetValue(test, null);

Console.WriteLine("Done");
Console.Read();
}
}

Saturday, January 12, 2008

Configure Log4Net To Log Different Files For Different Projects

Log4Net is a free logging helper running in .NET environment (ported from Log4j). By configuration setting you can log message to files, event log, console window, etc. The common usage is logging to one file, but you can also separate the log files for different projects if you want. Following configuration is an example to separate web UI project logs and backend class library logs:
  <log4net>
<appender name="RollingFileAppenderBackend" type="log4net.Appender.RollingFileAppender">
<file value="C:\inetpub\logs\Backend.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1000KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<appender name="RollingFileAppenderWebUI" type="log4net.Appender.RollingFileAppender">
<file value="C:\inetpub\logs\WebUI.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1000KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<logger name="Backendlogger">
<level value="DEBUG" />
<appender-ref ref="RollingFileAppenderBackend" />
<appender-ref ref="EventLogAppender" />
<appender-ref ref="ConsoleAppender" />
</logger>
<logger name="UILogger">
<level value="DEBUG" />
<appender-ref ref="RollingFileAppenderWebUI" />
<appender-ref ref="ConsoleAppender" />
</logger>
</log4net>

Thursday, October 25, 2007

Javascript Random Text

Following javascript is to create some mock contacts for UI testing purpose:
    function createMockContacts(contactNumber) {
        var contacts = new Array();
        for (var i = 0; i < contactNumber; i++) {
            var contact = new Object();
            contact.Name = randomText(8);
            contact.Company = randomText(16);
            contact.Phone = randomText(9, "phone");
            contact.Email = randomText(9, "email");
            contact.Address = randomText(20);
            contacts.push(contact);
        }
        return contacts;
    }
    function randomText(length, type) {
        if ( type == "phone") 
            var chars = "1234567890";
        else 
            var chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        var text = "";
        for (var i = 0; i < length; i++) {
            text += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        if (type == "email" && text.length > 3)
            text = text.slice(0, 3) + "@" + text.slice(3) + ".com";

        return text;
    }
Return is an array of javascript objects:
 contacts[0].Name = "7k15zlQQ"
 contacts[0].Company = "sTOX2EVFhsBXJFIn"
 contacts[0].Phone = "881414055"
 contacts[0].Email = "3Ic@Adu2N3.com"
 contacts[0].Address = "lN3kSwJCJC3m7gC8zdZy" 
 contacts[1].Name = "IZYkS3gI"
 ...

Thursday, October 11, 2007

.NET Repository Pattern

Domain Driven Design (DDD) attracts quite a lot of attentions in recent years. Repository pattern is one important pattern in DDD. It mediates between the domain and data mapping layers, or a simple delegation of responsibility away from the domain objects.

Consider reading GeoCode from database, we may have following code:
public class GeoCode
{
public double Latitude;
public double Longitude;
public GeoCode(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}

public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
string connectionString = "....";
string sqlQuery = "SELECT Latitude, Longitude FROM GeoCode WHERE PostalCode = @PostalCode";
try
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand DBCommand = new SqlCommand(sqlQuery, connection);
DBCommand.Parameters.AddWithValue("@PostalCode", postalCode);
using (SqlDataReader reader = DBCommand.ExecuteReader())
{
if (reader.HasRows)
{
gc = new GeoCode(Convert.ToDouble(reader["Latitude"]), Convert.ToDouble(reader["Longitude"]));
}
}
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
return gc;
}
}
Such code works but it's not flexible and it has direct dependency to the backend store. With repository pattern we need to declare an interface and have a separate implementation:
public class GeoCode
{
public double Latitude;
public double Longitude;
public GeoCode(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
}

public interface IGeoCodeRepository
{
GeoCode GetByPostalCode(string postalCode);
}

public class SQLServerGeoCodeRepository : IGeoCodeRepository
{
private string ConnectionString;
public SQLServerGeoCodeRepository()
{
ConnectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
}
public SQLServerGeoCodeRepository(string connectionString)
{
ConnectionString = connectionString;
}

public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
string sqlQuery = "SELECT Latitude, Longitude FROM GeoCode WHERE PostalCode = @PostalCode";
try
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
SqlCommand DBCommand = new SqlCommand(sqlQuery, connection);
DBCommand.Parameters.AddWithValue("@PostalCode", postalCode);
using (SqlDataReader reader = DBCommand.ExecuteReader())
{
if (reader.HasRows)
{
gc = new GeoCode(Convert.ToDouble(reader["Latitude"]), Convert.ToDouble(reader["Longitude"]));
}
}
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
return gc;
}
}
A wrapper class in service layer exposes all related functions:
public class GeoCodeService
{
private IGeoCodeRepository _repository;
public GeoCodeService(IGeoCodeRepository repository)
{
_repository = repository;
}

public GeoCode GetByPostalCode(string postalCode)
{
return _repository.GetByPostalCode(postalCode);
}
}

class Program
{
static void Main(string[] args)
{
IGeoCodeRepository repository = new SQLServerGeoCodeRepository();
GeoCodeService service = new GeoCodeService(repository);
GeoCode gc = service.GetByPostalCode("A1A1A1");
}
}
With repository pattern we can easily extend current implementation. For example, if we need to use web service to get the geo code, we can simply add a new implementation without changing any existing code:
public class WebServiceGeoCodeRepository : IGeoCodeRepository
{
private string WebServiceUrl;
public WebServiceGeoCodeRepository()
{
WebServiceUrl = ConfigurationManager.AppSettings["WebServiceUrl"];
}
public WebServiceGeoCodeRepository(string webServiceUrl)
{
WebServiceUrl = webServiceUrl;
}

public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
//Get GeoCode by web service
return gc;
}
}

public class Program
{
static void Main(string[] args)
{
IGeoCodeRepository repository = new WebServiceGeoCodeRepository();
GeoCodeService service = new GeoCodeService(repository);
GeoCode gc = service.GetByPostalCode("A1A1A1");
}
}
The other advantage of using repository pattern is the testability. For example we can create a fake data repository:
public class FakeGeoCodeRepository : IGeoCodeRepository
{
public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
double fakeCode = 111.111;
if (postalCode.StartsWith("A", StringComparison.InvariantCultureIgnoreCase))
fakeCode = 123.123;
gc = new GeoCode(fakeCode, fakeCode);
return gc;
}
}
The unit test becomes simple:
 [TestMethod()]
public void GetByPostalCodeTest()
{
IGeoCodeRepository repository = new FakeGeoCodeRepository();
GeoCodeService target = new GeoCodeService(repository);
string postalCode = "M1M1M1";
double expected = 111.111;
GeoCode actual;
actual = target.GetByPostalCode(postalCode);
Assert.AreEqual(expected, actual.Latitude);
Assert.AreEqual(expected, actual.Longitude);

postalCode = "A1A1A1";
expected = 123.123;
actual = target.GetByPostalCode(postalCode);
Assert.AreEqual(expected, actual.Latitude);
Assert.AreEqual(expected, actual.Longitude);
}
Related topics:
http://martinfowler.com/eaaCatalog/repository.html
http://martinfowler.com/articles/injection.html

Wednesday, September 26, 2007

ASP.NET Impersonate Application Pool Account To Access Database

In an ASP.NET web application, we use a fixed account to connect to SQL server. Although we could encrypt the user name and password in the connection string, admin still suggested to use Windows Authentication so that there's no any user credential information stored in the file system.

One simple way to achieve that is create an application pool for the web app with a service account, and grant the permission for that service account inside the SQL server, then wrap all ADO.NET code inside a System.Web.Hosting.HostingEnvironment.Impersonate() block:
    using (System.Web.Hosting.HostingEnvironment.Impersonate())
{
using (SqlConnection connection = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(spStoredProcedureName, connection);
//... other ADO.NET code
}
}

Thursday, August 02, 2007

Array.ConvertAll and List.ConvertAll

Just noticed this handy method:
using System;
using System.Collections.Generic;

class Test
{
static void Main()
{
int[] numbers = new int[] { 65, 66, 67 };
char[] chars = Array.ConvertAll(numbers, delegate(int number)
{ return (char)number; });

//Print "ABC"
Array.ForEach(chars, delegate(char ch) { Console.Write(ch); });

Console.WriteLine();
List<int> numberList = new List<int>(numbers);
List<char> charList = numberList.ConvertAll(delegate(int number)
{ return (char)number; });

//Print "ABC"
charList.ForEach(delegate(char ch) { Console.Write(ch); });

Console.Read();
}
}