Sergey Akopov

Perpetual adventurer and purveyor of awesomeness.

RESTful validation with ASP.NET Web API and FluentValidation

I wanted to briefly talk about request validation in RESTful APIs today. I've had a lot of luck with FluentValidation in the past few years both in API and MVC worlds so my primary focus here is integrating it into a typical Web API project and configuring a few things to output truly RESTful validation errors. So, let's get to it! What are we trying to achieve?

Let's think of the following request/response scenario. A client issues request to the API to create a new employee, but doesn't specify the department. The API fails the request and presents a client-friendly error message indicating that a department is a required in order to create new employee resource. This scenario is outline below.

// Request
POST http://my-domain/api/employee
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 401
{
    "message": "The request is invalid.",
    "errors": {
        "department": [
            "'Department' should not be empty."
        ]
    }
}

To make this happen, we'll start by adding FluentValidation to our project. I am making an assumption that the project is using a Dependency Injection framework of some sort. In my case it's Ninject, but any code snippets here can be easily transferred to other Dependency Injection frameworks of your choosing. We'll begin by writing our employee controller.

[RoutePrefix("api/employee")]
public class EmployeeController : ApiController
{
    [HttpPost]
    [Route("")]
    [ResponseType(typeof(Employee))]
    public IHttpActionResult Create(Employee employee)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }    

        // Create new employee

        return Created();
    }
}

// Model
public class Employee
{
    public string Name { get; set; }
    public string Department { get; set; }
    public string Position { get; set; }
}

Next up is the employee validator.

public class EmployeeValidator : AbstractValidator<Employee>
{
    public EmployeeValidator()
    {
        RuleFor(model => model.Name)
            .NotEmpty();

        RuleFor(model => model.Department)
            .NotEmpty();

        RuleFor(model => model.Position)
            .NotEmpty();            
    }
}

The snippet above is a trivial FluentValidation validator, which will simply check that each property of the employee resource is available on the request. The results of the validation process will be written to ModelState by FluentValidation. The controller action checks whether ModelState is valid and if it isn't, returns it on the response to client with status code of 401. At this point all of the essential pieces are in place and we just need to tie things together.

The beautiful thing about FluentValidation is that it doesn't require any attributes on your model or on controller actions, but only if you're using Dependency Injection. This is the magical part which keeps your models and validators completely decoupled. However, if you aren't using Dependency Injection than you need to add [Validator(typeof(EmployeeValidator))] to your Employee model.

Since I'm using Ninject, we're going to wire up FluentValidation via Dependency Injection. We'll begin by scanning the API assembly (or wherever your validators live) for validators and register them with the Ninject Kernel on per-request basis, meaning a new instance of a validator will be created on each HTTP request. To do this I'm using AssemblyScanner which ships with FluentValidation. You're going to want to run this wherever you register all of your dependencies with Ninject.

private static void RegisterValidators(IKernel kernel)
{
    AssemblyScanner
        .FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
        .ForEach(result => kernel.Bind(result.InterfaceType)
        .To(result.ValidatorType)
        .InRequestScope());
}

Now that we have all validators in the Ninject Kernel, we need a way to tell FluentValidation how to retrieve them when needed. To do this we'll inherit from ValidatorFactoryBase in FluentValidation to spin up new validators from the Ninject Kernel.

public class NinjectValidatorFactory : ValidatorFactoryBase
{
    public NinjectValidatorFactory(IKernel kernel)
    {
        Kernel = kernel;
    }

    public IKernel Kernel
    {
        get;
        set;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        if (((IList<IBinding>)Kernel.GetBindings(validatorType)).Count == 0)
        {
            return null;
        }

        return Kernel.Get(validatorType) as IValidator;
    }
}

Lastly, we'll need to configure FluentValidation to use our custom validator factory. Once again, since this will need an instance of the Ninject Kernel, you'll probably want to run this somewhere where you're registering your dependencies.

FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, provider => provider.ValidatorFactory = new NinjectValidatorFactory(kernel));

At this point, we can POST a request to our API and get a response that looks like the one below.

// Request
POST http://my-domain/api/employee
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 401
{
    "message": "The request is invalid.",
    "modelState": {
        "employee.Department": [
            "'Department' should not be empty."
        ]
    }
}

This is great, but what's this modelState business? Sigh... This is our serialized ModelState object. Sounds like we'll have to do some tweaks to get this to output as errors array instead. We'll use an action filter for this.

public class RestfulModelStateFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateResponse(
                HttpStatusCode.BadRequest, new ApiResourceValidationErrorWrapper(actionContext.ModelState));
        }
    }
}

So, what's happening here is we're grabbing the ModelState before the controller action is executed and after FluentValidation validator runs and writes the results to ModelState. If validation fails we pass ModelState to ApiResourceValidationErrorWrapper, which serializes it just the way we want before putting it back on the response for calling client. Here is how this part looks like.

public class ApiResourceValidationErrorWrapper
{
    private const string ErrorMessage = "The request is invalid.";

    private const string MissingPropertyError = "Undefined error.";

    public ApiResourceValidationErrorWrapper(ModelStateDictionary modelState)
    {
        Message = ErrorMessage;
        SerializeModelState(modelState);
    }

    public ApiResourceValidationErrorWrapper(string message, ModelStateDictionary modelState)
    {
        Message = message;
        SerializeModelState(modelState);
    }

    public string Message { get; private set; }

    public IDictionary<string, IEnumerable<string>> Errors { get; private set; }

    private void SerializeModelState(ModelStateDictionary modelState)
    {
        Errors = new Dictionary<string, IEnumerable<string>>();

        foreach (var keyModelStatePair in modelState)
        {
            var key = keyModelStatePair.Key;

            var errors = keyModelStatePair.Value.Errors;

            if (errors != null && errors.Count > 0)
            {
                IEnumerable<string> errorMessages = errors.Select(
                    error => string.IsNullOrEmpty(error.ErrorMessage)
                                 ? MissingPropertyError
                                 : error.ErrorMessage).ToArray();

                Errors.Add(key, errorMessages);
            }
        }
    }
}

We can now remove the explicit ModelState check in the controller action because it's already handled for us in the action filter! The only thing left is to register the action filter with the global filter collection in your Web API config.

config.Filters.Add(new RestfulModelStateFilterAttribute());

With all of these changes in place, we finally have our validation response looking how we want! Yippee!

// Request
POST http://my-domain/api/employee
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 401
{
    "message": "The request is invalid.",
    "errors": {
        "employee.Department": [
            "'Department' should not be empty."
        ]
    }
}

Discuss »

Extending ASP.NET MVC Redirects with Confirmations

One of the most common tasks in any application is displaying confirmation messages after execution of a back-end operation. The standard way to accomplish this is to use TempData to persist messages from one action to another during a redirect. What I'd like to show is a very crafty way of extending the redirect behavior in MVC to include confirmation messages via TempData.

What we're looking to do is write extension methods for RedirectToRouteResult and RedirectResult named WithConfirmation to append custom confirmation message to TempData dictionary. The end result should look like shown in the following snippet.

[HttpPost]
public ActionResult DoSomething()
{
    return this
        .RedirectToAction("Index", "Home")
        .WithConfirmation("Insert message here");
}

To accomplish this we'll want to implement a custom ActionResult to act as the pass-through layer, which adds the confirmation message to TempData and executes the original redirect result.

public class RedirectWithConfirmationMessageResult : ActionResult
{
    private readonly string message;

    public RedirectWithConfirmationMessageResult(ActionResult redirectBaseResult, string message)
    {
        BaseResult = redirectBaseResult;
        this.message = message;
    }

    public ActionResult BaseResult { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.TempData["Message.Notification"] = this.message;

        BaseResult.ExecuteResult(context);
    }
}

Now, we'll write the extension methods.

public static class RedirectExtensions
{
    public static RedirectWithConfirmationMessageResult WithConfirmation(this RedirectToRouteResult instance, string message)
    {
        return new RedirectWithConfirmationMessageResult(instance, message);
    }

    public static RedirectWithConfirmationMessageResult WithConfirmation(this RedirectResult instance, string message)
    {
        return new RedirectWithConfirmationMessageResult(instance, message);
    }        
}

While this example shows only confirmation messages, it can be easily extended to support any message types. I'll leave that as an exercise for the reader.

Discuss »

Tamper-proof hidden fields in ASP.NET MVC

A common practice a lot of web developers employ is storing random referential data in hidden fields in the views. More often than not, this includes identifiers to database records. Any relatively knowledgeable user can alter these fields with an in-browser DOM editor. Hence, a good rule of thumb is to always validate every piece of data received from the user and make sure they're persisting all data as they should.

What I'd like to show is a simple way to help mitigate the tampering of the data in hidden fields by generating an encrypted token of the original value and then validating that the value still matches the token when it's submitted by the user. This works very similar to the AntiForgeryToken mechanism already implemented in MVC. Just to make it clear, this trick doesn't alleviate the need for data validation. It is merely a way to provide extra layer of protection.

Let's start by creating a new set of HTML helpers to spin-up protected hidden fields. Each helper will generate two hidden fields - one with the original value and one with the encrypted Base64 string of the original value (the token). If the attacker tries to modify a protected hidden field, they'd also have to provide the correct encrypted token.

public static class HtmlExtensions
{
    public static MvcHtmlString SecureHidden(this HtmlHelper htmlHelper, string name)
    {
        return SecureHidden(htmlHelper, name, value: null, htmlAttributes: null);
    }

    public static MvcHtmlString SecureHidden(this HtmlHelper htmlHelper, string name, object value)
    {
        return SecureHidden(htmlHelper, name, value, htmlAttributes: null);
    }

    public static MvcHtmlString SecureHidden(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
    {
        return SecureHidden(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public static MvcHtmlString SecureHidden(
        this HtmlHelper htmlHelper,
        string name,
        object value,
        IDictionary<string, object> htmlAttributes)
    {
        return SecureHiddenHelper(htmlHelper, value: value, name: name, htmlAttributes: htmlAttributes);
    }

    public static MvcHtmlString SecureHiddenFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression)
    {
        return SecureHiddenFor(htmlHelper, expression, null);
    }

    public static MvcHtmlString SecureHiddenFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        object htmlAttributes)
    {
        return SecureHiddenFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public static MvcHtmlString SecureHiddenFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        IDictionary<string, object> htmlAttributes)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        return SecureHiddenHelper(htmlHelper, metadata.Model, ExpressionHelper.GetExpressionText(expression), htmlAttributes);
    }

    public static MvcHtmlString DisableIf(this MvcHtmlString instance, Func<bool> expression)
    {
        const string Disabled = "\"disabled\"";

        if (expression.Invoke())
        {
            var html = instance.ToString();
            html = html.Insert(html.IndexOf(">", StringComparison.Ordinal), " disabled= " + Disabled);
            return new MvcHtmlString(html);
        }

        return instance;
    }

    private static MvcHtmlString SecureHiddenHelper(
        HtmlHelper htmlHelper,
        object value,
        string name,
        IDictionary<string, object> htmlAttributes)
    {
        var binaryValue = value as Binary;

        if (binaryValue != null)
        {
            value = binaryValue.ToArray();
        }

        var byteArrayValue = value as byte[];

        if (byteArrayValue != null)
        {
            value = Convert.ToBase64String(byteArrayValue);
        }

        return InputHelper(htmlHelper, name, value, setId: true, format: null, htmlAttributes: htmlAttributes);
    }

    private static MvcHtmlString InputHelper(
        HtmlHelper htmlHelper,
        string name,
        object value,
        bool setId,
        string format,
        IDictionary<string, object> htmlAttributes)
    {
        var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        if (string.IsNullOrEmpty(fullName))
        {
            throw new ArgumentException("name");
        }

        var inputItemBuilder = new StringBuilder();

        var hiddenInput = new TagBuilder("input");
        hiddenInput.MergeAttributes(htmlAttributes);
        hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
        hiddenInput.MergeAttribute("name", fullName, true);
        hiddenInput.MergeAttribute("value", htmlHelper.FormatValue(value, format));

        var hiddenInputHash = new TagBuilder("input");
        hiddenInputHash.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
        hiddenInputHash.MergeAttribute("name", string.Format("__{0}Token", fullName), true);

        var identity = htmlHelper.ViewContext.HttpContext.User.Identity;

        if (!string.IsNullOrEmpty(identity.Name))
        {
            value = string.Format("{0}_{1}", identity.Name, value);
        }

        var encodedValue = Encoding.Unicode.GetBytes(htmlHelper.FormatValue(value, format));

        hiddenInputHash.MergeAttribute(
            "value",
            Convert.ToBase64String(MachineKey.Protect(encodedValue, "Protected Hidden Input Token")));

        if (setId)
        {
            hiddenInput.GenerateId(fullName);
            hiddenInputHash.GenerateId(string.Format("__{0}Token", fullName));
        }

        inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
        inputItemBuilder.Append(hiddenInputHash.ToString(TagRenderMode.SelfClosing));

        return MvcHtmlString.Create(inputItemBuilder.ToString());
    }
}

The class above provides multiple overloads of SecureHidden and SecureHiddenFor, which allows the consumer to generate a protected hidden for a property of a view model or an arbitrary form field name and value. The InputHelper method does all of the grunt work. I'm using MachineKey for all encryption purposes. This is the same encryption mechanism used internally by the framework to encrypt authentication cookies and view state.

In addition, I'm relying on the current user identity to provide user-specific encryption by combining username with the original value. This makes it more difficult to use the encrypted token across accounts. However, this limits the use of the helper to authenticated scenarious. You could probably use session ID here or something similar. In fact, if I remember correctly, the AntiForgeryToken implementation uses session for this purpose.

The next step is to register these helpers with Razor. Head on over to your Views folder and open Web.config. In the <namespace> node of <system.web.webPages.razor> reference the namespace containing your new helpers. You should now be able to use your helpers as you would any other native helper in MVC.

@Html.SecureHiddenFor(model => model.SomeProperty)

The above should result in generated HTML similar to the one below.

<input id="SomeProperty" name="SomeProperty" type="hidden" value="I love lamp">
<input name="__SomePropertyToken" type="hidden" value="zqb7MIL2Y5F3jL96ncdSZOmetL8g8RAWZP8Y/w/jUAKJ89GcUViRWOZ/XtQhtICMFZb4sQtZLOpqK/WyC0TFP0B6r+3nObFGDjb0U459yzQbadC4+DLIsTmhyYeT+ZT+bnW1AEP2fgVyXXSduYIf5vns7g9nhRWTgJo8xF6NQyT6kNgyl5puq+BYc8dfhMXn">

Finally, we need to validate all protected hidden fields. This is done by implementing a filter attribute for controller actions, which would specify the names of the hidden fields which need to be validated. The validation process is very trivial. We'll just decrypt the token and compare it to the current username and hidden field value combination. If they do not match we'll throw an exception. Otherwise everything continues as it should.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ValidateSecureHiddenInputsAttribute : FilterAttribute, IAuthorizationFilter
{
    private readonly string[] properties;

    public ValidateSecureHiddenInputsAttribute(params string[] properties)
    {
        if (properties == null || !properties.Any())
        {
            throw new ArgumentException("properties");
        }

        this.properties = properties;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        this.properties.ToList().ForEach(property => Validate(filterContext, property));
    }

    private static void Validate(AuthorizationContext filterContext, string property)
    {
        var protectedValue = filterContext.HttpContext.Request.Form[string.Format("__{0}Token", property)];
        var decodedValue = Convert.FromBase64String(protectedValue);

        var decryptedValue = MachineKey.Unprotect(decodedValue, "Protected Hidden Input Token");

        if (decryptedValue == null)
        {
            throw new HttpSecureHiddenInputException("A required security token was not supplied or was invalid.");
        }

        protectedValue = Encoding.Unicode.GetString(decryptedValue);

        var originalValue = filterContext.HttpContext.Request.Form[property];

        var identity = filterContext.HttpContext.User.Identity;

        if (!string.IsNullOrEmpty(identity.Name))
        {
            originalValue = string.Format("{0}_{1}", identity.Name, originalValue);
        }

        if (!protectedValue.Equals(originalValue))
        {
            throw new HttpSecureHiddenInputException("A required security token was not supplied or was invalid.");
        }
    }
}

public class HttpSecureHiddenInputException : Exception
{
    public HttpSecureHiddenInputException(string message) : base(message)
    {
    }
}

With all pieces in place and assuming you created a protected hidden field...

@Html.SecureHiddenFor(model => model.SomeProperty)

You can validate it by decorating your target action.

[ValidateSecureHiddenInputs("SomeProperty")]
public ActionResult Index(MyViewModel model)
{
    return View();
}

Happy coding!

Discuss »

How I do dependency injection

It seems that dependency injection (DI) as software development pattern has become the standard for many open source projects and software companies. Indeed, if you practice any kind of tiered development and employ test-driven practices, DI is likely a great direction for your projects.

When done right, DI allows developers to rather effortlessly extrapolate a complex design into well-defined and easily consumable modules. It gives the flexibility in how objects are interconnected thus allowing greater configuration facility for different modes of operation of your application. Having said all of this, the real trouble of DI is that in most cases the concept behind it is misunderstood and it becomes a major factor of absue.

Here is just a few problems with dependency injection, which I come across time and time again.

  • DI container is used as service locator.
  • Object constructors explode with dependencies.
  • Everything is abstracted and injected.
  • The application tier has a reference to everything, even when it shouldn't by-design, simply because it is configuring the DI container.

I have come up with a few practices over the years to minimize the negative impact of DI and basically lock it down in an effort to prevent abuse as much as possible.

The application loader

One of the things I completely despise about DI is the fact that the application tier is forced to reference every single component just because it is responsible for configuring the container. I think this smells.

In a typical tiered design all logic runs through some kind of service or business tier. The application tier should have no knowledge of anything below that architectural level. By introducing unnecessary dependencies at this level there are more chances for accidental use of any components contained within any architecturally restricted tiers.

To prevent this from happening I started using a separate project I call "The Loader." This project is responsible for registering and configuring components on behalf of the application tier by providing a single point of access, called "The Kernel", for convention-based component registration. Here is what this looks like in a sample project.

alt text

In the image above, the DependencyModule class implements Autofac's Module to register the dependency graph. The loader project now has a reference to everything instead of the application layer, thus greatly diminishing the chance of abuse. And it just makes the application tier much cleaner.

public sealed class DependencyModule : Module
{
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);

            builder.Register(c => new SqlConnection(ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString))
                   .As<IDbConnection>()
                   .InstancePerHttpRequest();

            builder.RegisterType<MemcacheProvider>()
                   .As<ICacheProvider>()
                   .SingleInstance();

            ...
        }
}

The interesting bit here is the Kernel class which provides convention-based component registration for the consumer (a.k.a your app).

public sealed class Kernel
{
    private static readonly ContainerBuilder Builder;

    static Kernel()
    {
        Builder = new ContainerBuilder();
        Builder.RegisterModule(new DependencyModule());
    }    

    public static void RegisterMvcControllers(Assembly assembly)
    {
        Builder.RegisterControllers(assembly);
    }

    public static void RegisterTasks(Assembly assembly)
    {
        Builder.RegisterAssemblyTypes(assembly)
               .Where(task => typeof (IBootstrapperTask).IsAssignableFrom(task))
               .As<IBootstrapperTask>()
               .SingleInstance();
    }

    public static void RegisterMaps(Assembly assembly)
    {
        assembly.GetTypes()
                .Where(type => typeof (Profile).IsAssignableFrom(type))
                .ToList()
                .ForEach(type => Mapper.AddProfile((Profile) Activator.CreateInstance(type)));
    }

    public static void Start()
    {
        var container = Builder.Build();

        var mvcResolver = new AutofacDependencyResolver(container);

        DependencyResolver.SetResolver(mvcResolver);
    }
}

The application tier simply references the loader project and calls the Kernel to configure itself.

Kernel.RegisterMvcControllers(Assembly.GetExecutingAssembly());
Kernel.RegisterTasks(Assembly.GetExecutingAssembly());
Kernel.RegisterMaps(Assembly.GetExecutingAssembly());
Kernel.Start();

The application tier doesn't even know it's using dependency injection container at all. Therefore, there is no way to use the DI container explicitly anywhere in the application tier. This removes the anti-pattern of using container as service locator.

While this is a very simple example of the kernel, this can be extended to cover more complex configuration scenarios where an application can configure components in a variety of different ways and take a different logical path during execution.

I'm sure there are some edge cases, but so far I have successfully used this on multiple large projects with great results.

The dependency overflow

Another thing to easily do wrong when using dependency injection is to mindlessly add constructor dependencies because, well, it's so easy to do because the container will just resolve them.

The issue here is to realize that your architecture should be laid out without any reliance on any kind of inversion of control principles. Set a guideline for the number of dependencies per each component. If that number is exceeded it is time to break things up because your component is probably doing too much work. Always keep in mind the single responsibility principle.

What I found works rather well for me is to strictly adhere to the set number of dependencies in any component below the application tier while always strive to maintain this rule at the application tier (sometimes this isn't possible due to strict design specs). The thought process here is to make sure you can easily construct and test any component below the application tier. As long as this is possible, the way in which components are stitched together at the application tier isn't a very major issue.

The injection craze

DO NOT abstract and inject everything on earth. It is highly unlikely that you're going to switch database technologies, so what's the point of creating a highly flexible repository that works with ungodly amount of data access libraries? Not only are you wasting time, your abstraction is probably not going to be all-encompasing and will inevitably leak like a sieve.

Keep your design tight and sane.

Discuss »

Bootstrapping your .NET MVC apps with executable tasks

When working on large projects in ASP.NET MVC I often try to automate the development workflow as much as possible. One of such automation points is the bootstrapping logic required to fire up a typical MVC app. I'm referring to the following tasks:

  • Registering bundles.
  • Registering global filters.
  • Registering routes.
  • Registering areas.

I always find that this list easily blows up as the number of components grows. One great way to automate the registration of components at start-up is by using the Command pattern.

We'll start by creating the bootstrapper task interface.

public interface IBootstrapperTask
{
    void Run();
}

The next step is to convert all config code located in App_Start to tasks. Here is an example of the bundle registrations.

public class RegisterBundles : IBootstrapperTask
{
    public void Run()
    {
        var bundles = BundleTable.Bundles;

        bundles.Add(
            new ScriptBundle("~/assets/js/app").Include(
                "~/assets/some/kinda/js/script.js"));
    }
}

Now we just need to execute all bootstrapper tasks. This can be done using assembly scanning and reflection in Global.asax.

Assembly.GetExecutingAssembly()
        .GetTypes()
        .Where(task => typeof (IBootstrapperTask).IsAssignableFrom(task))
        .ToList()
        .ForEach(task => task.Run());

And this is all it takes! But let's not stop here. In more complicated cases your bootstrapper tasks could have dependencies in the constructor. We can solve this using dependency injection.

I will show how to handle this using Autofac. You can do a similar thing with any other dependency injection container of your choosing. Let's scan the assembly and register all IBootstrapperTask implementations with the Autofac container.

public static void RegisterTasks(Assembly assembly)
{
    Builder.RegisterAssemblyTypes(assembly)
           .Where(task => typeof (IBootstrapperTask).IsAssignableFrom(task))
           .As<IBootstrapperTask>()
           .SingleInstance();
}

You can call this from Global.asax or wherever you have your start-up code.

RegisterTasks(Assembly.GetExecutingAssembly());

Once the Autofac container is built, all tasks can execute.

container.Resolve<IEnumerable<IBootstrapperTask>>()
         .ToList()
         .ForEach(task => task.Run());

Now you can just drop new bootstrapper tasks in your App_Start (or wherever) and they will be executed automatically on start-up.

Discuss »