Dependency Injection with Static constructor?
The short answer to the question is no. It simply does not make sense. Since the static constructor gets called by CLR , the static constructor can't have parameters. Therefore, the constructor injection will not be possible.
I normally create a helper class for configuration data as a facade for convenient access to data such as xml stored in web.config. This approach gives me a couple of advantages.
First, I can immediately start working with dummy configuration which is hard coded in the helper class before I actually create a repository for real configuration data.
Second, the configuration data is not all over the place since the helper object is a single place to manage all configuration data. If I have to make some radical changes to the configuration repository such as switching from web.config to a database, all I can replace is the helper class.
The helper class has a static method that returns the image uri based on the image id. And the image service format is hard coded
public class ConfigurationHelper { public static string GetImageUri(long imageId) { // TODO: Get the service address format from web.config return string.Format("http://imageservice/{0}.xml", imageId); } }
I soon encountered an issue when I tried to create unit tests for the asp.net mvc project. the ConfigurationHelper contains only static methods.
Image action in the home controller
public ActionResult Image(string id) { long imageId; if (!long.TryParse(id, out imageId)) { throw new System.ArgumentException(); } // Construct image service url. ViewBag.ImageServiceUri = ConfigurationHelper.GetImageUri(imageId); return View(); }
I had this issue before many times. Basically, I had to duplicate the configuration data in the unit tests project. It's not hard to do, except it's a pain. You have to make sure the configuration data is synced up with the test project. I am pretty sure there is a way to copy the configuration to the test project automatically but it's still not a good idea that your unit tests are dependent on the configuration data. So I decided to mock the ConfigurationHelper class in the test project so it will never fail. Therefore, it will never break the unit tests.
So I refactored the code as below. And then I got stuck again here.
public class ConfigurationHelper { private static IConfigurationRepository configRepository = null; /// /// Get the image service address based on the configuration /// /// an image id /// the uri for the specified image. public static string GetImageUri(long id) { return string.Format(configRepository.ImageServiceAddressFormat, id); } }
public interface IConfigurationRepository { string ImageServiceAddressFormat {get;} }
I want to find out a way to inject an implementation of IConfigurationRepository and below is a hack. I created a static method called "Inject".
public class ConfigurationHelper { private static IConfigurationRepository configRepository = null; /// /// Injects configuration repository /// /// configuration repository public static void Inject(IConfigurationRepository repository) { if (configRepository == null) { configRepository = repository; } } /// /// Get the image service address based on the configuration /// /// an image id /// the uri for the specified image. public static string GetImageUri(long id) { return string.Format(configRepository.ImageServiceAddressFormat, id); } }
Manually inject the dependency on Global.aspx.cs
IUnityContainer container = new UnityContainer() container.LoadConfiguration("default"); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); // TODO: It's a manual injection because the static constructor does not take parameters // Let me know if you know a better way to inject dependancy for static methods ConfigurationHelper.Inject(DependencyResolver.Current.GetService());
Mock ConfigurationHelper using Moq framework
private void SetUpConfigurationHelper() { var configStub = new Mock(); configStub.Setup(x => x.ImageServiceAddressFormat) .Returns(ImageServiceFormat); ConfigurationHelper.Inject(configStub.Object); }
[TestCase("1")] [TestCase("268435456")] [TestCase("68719476736")] public void HomeController_Image_Test(string id) { var stub = new Mock(); using (var controller = new HomeController(stub.Object)) { var result = controller.Image(id) as ViewResult; Assert.IsInstanceOf(result, "The result should be a ViewResult type instance"); Assert.IsTrue(!string.IsNullOrEmpty(result.ViewBag.ImageServiceUri)); Assert.AreEqual(string.Format(ImageServiceFormat, id), result.ViewBag.ImageServiceUri); } }