Others Talk, We Listen.

ASP.NET Web API and MVC Basic Infrastructure By Example

by John Nein on May 06, 2014

In this post I will be going over some of the basic infrastructure and design patterns for both ASP.NET Web API and ASP.NET MVC.  In the example I am creating I will be using Web API 2 and MVC 5 and creating the project from the Single Page Application (SPA) template in Visual Studio 2013.  Although some of the concepts in this post will be more applicable to either Web API or MVC, using the SPA template will allow me to demonstrate some of the concepts using both of the frameworks.

Infrastructure Overview

Infrastructure is the foundation of an application.  I use this term broadly to refer to any of the core cross-cutting functionalities that are required for scalable applications.  This includes:

  • Logging
  • Exception handling
  • Application settings
  • Inversion of control (IOC)/dependency injection
  • Object-relational mapping (ORM)
  • Caching

Design Patterns Overview

Design patterns are basic blueprints for architecture.  Design patterns help to provide separation of concerns, flexibility, organization, and extensibility across an application.  In this post I will be going over some basic design patterns used within MVC and Web API:

  • Site/domain separation
  • Repository/factory pattern
  • Model-view-controller (built into the framework, as the name would suggest)

Implementation

We will be implementing a demo in 8 steps to demonstrate basic MVC and Web API infrastructure:

1. Setting up the Projects
2. Setting up Entity Framework
3. Outlining Application Needs with Interfaces
4. Logging
5. Exception Handling
6. Implementing the Interfaces
7. IOC/dependency Injection
8. Utilizing the Infrastructure

 

1. Setting up the Projects

Create a new ASP.NET web application in Visual Studio using the SPA template.  My application will be called "MyNotebook" so the project is named "MyNotebook.Site" and I am creating a directory for the solution called "MyNotebook."

Once the Site project is created, create another project in the solution for the Domain.  This will be a class library project and for my implementation it will be called "MyNotebook.Domain" to keep consistent with the Site project.

Although the Site project already uses Entity Framework for membership from the SPA template, we will be accessing our database through the Domain project and need to install the EntityFramework NuGet package into that project.  I am using Entity Framework 6.0.  To utilize some of the bootstrapped membership/identity code that comes with the initial SPA template, we will also install the EntityFramework ASP.NET Identity NuGet package into the Domain project.

2. Setting up Entity Framework

Now that the Domain project is created, we can setup the object-relational mapping (ORM) framework, Entity Framework (EF).  Entity Framework isn't the only ORM, and there are some good alternatives including the popular NHibernate. EF is, however, the recommended ORM from Microsoft and it is my personal preference.

EF allows abstracted programmatic access to databases through contexts.  We will create our context in a new folder called "Entities" under the Domain project.  The new context will be called "MyNotebookDbContext" for my application and will inherit from the IdentityDbContext class in EF Identity.  This specific DbContext class is used for integrating EF with membership providers.  Finally, we will call the base constructor with the default connection name as specified in the web.config file, "DefaultConnection."

    public class MyNotebookDbContext : IdentityDbContext
     {
            public MyNotebookDbContext()
                : base("DefaultConnection")
            { }

    ...

    }

Now that the context is created, I will create the entity class that will represent one of the objects my application will eventually use.  This will be created in the same Entities folder:

    public class Note
    {
        [Key]
        public int Id { get; set; }
        [Required]
        [StringLength(200)]
        public string Heading { get; set; }
        [Required]
        public string Body { get; set; }
        [Required]
        public DateTime CreatedDate { get; set; }
        [Required]
        public DateTime LastModifiedDate { get; set; }
        [Required]
        [StringLength(36)]
        public string UserId { get; set; }
    }

You can see that there are attributes on the specific properties in my Note class.  Although these attributes are fairly self-explanatory, you can see a more comprehensive list of their meanings and other attributes here.

To link this object into EF, we will add this as a DbSet property to the MyNotebookDbContext class:

   public DbSet<Note> Notes { get; set; }

This will allow EF to recognize this new class and, using EF's code-first functionality, when the context is accessed, we can ask EF to create the database and table automatically.  This is done by creating a database initializer; I am creating mine within the same file as the database context:

    public class MyNotebookDbInitializer : DropCreateDatabaseAlways<MyNotebookDbContext>
    {

        protected override void Seed(MyNotebookDbContext context)
        {
            var user = new IdentityUser
            {
                UserName = "Admin"
            };
            new UserManager<IdentityUser>(new UserStore<IdentityUser>(context)).Create(user, "password");
            context.Notes.Add(new Note
            {
                Heading = "My First Note",
                Body = "This is a note.",
                CreatedDate = DateTime.Now,
                LastModifiedDate = DateTime.Now,
                UserId = user.Id
            });
            context.SaveChanges();
        }

    }

The two things you should notice in this class is that I am inheriting the DropCreateDatabaseAlways class.  There are two other commonly used classes: CreateDatabaseIfNotExists and DropCreateDatabaseAlways.  These classes do exactly as their names suggest.  The other thing that you should notice is the Seed() method.  This method is called whenever the database is recreated, and allows implanting some data into the database.  I added a new user, "admin," and created a new Note.  To bind this initializer to the database context, we can add a configuration into the web.config file, or add this line to the global.asax.cs Application_Start() method as I have done:

    Database.SetInitializer<MyNotebookDbContext>(new MyNotebookDbInitializer());

3. Outlining Application Needs with Interfaces

As discussed some in the introductory paragraph, we need to fulfill basic needs for our application to create our infrastructure.  To do this, we will be creating interfaces that outline what we expect we will need.  Obviously, these interfaces will be basic at the moment and will be iterated on as the application matures.

The first interfaces will provide the settings for our application.  I have created a "Settings" folder under both of the projects and created interfaces "IDomainSettings" and "ISiteSettings" under the respective new folders.  These will serve as abstractions of the settings of our application.  Here are the basic settings that I have created for the Domain project:

    bool EnableCaching { get; }
    int DefaultCacheExpirationInMin { get; }

And for the Site project:

    string LogFilePath { get; }

The next interface we will create is for the data that our application will use.  I have created a new class called "INotesRepository" in the Entities folder.  This repository will abstract the data access for projects calling into the Domain library, including our Site project.  I have included five basic methods to retrieve data in the interface:

    Note GetNote(int id);
    IEnumerable<Note> GetUsersNotes(string userId);
    int? CreateNote(Note note);
    void UpdateNote(Note note);
    void DeleteNode(int id);

The final interface is the caching interface.  Although caching can be done out-of-process, having in-process caching can provide a very granular level of caching with more flexibility and control.  I have created a Caching folder in the Domain project as well as an ICacheProvider interface within that folder.  This interface includes some basic caching actions:

    void Add(string key, object value);
    T Get<T>(string key) where T : class;
    T TryGet<T>(string key, Func<T> fallback) where T : class;
    void InvalidateCacheItem(string key);

The TryGet method will allow requesting a cached item and falling back to a provided function if it is a cache miss.  This will eliminate duplicated code throughout the project.

4. Logging

Logging is a critical functionality for any sized application.  It is necessary to recount what has happened in the application, good or bad (especially bad).  Logging in this demo will be slightly different than some of the other infrastructure components in that it will not be genericized into an interface.  I chose to do this because logging frameworks tend to be unique and abstracting that functionality into a common logging interface would remove the benefits of most of those logging implementations.  The specific logging library I am using for this project is nice because, although it will not be abstracted, it has the ability to publish events to the Windows Event Service.  Those published events can be picked up by another logging framework in the future if needed.

For the purposes of the demo, I am using Microsoft's Enterprise Library Semantic Logging Application Block.  This logging library lets us configure one EventSource for each project that will serve as a central logging utility class.  We can statically call methods that are named for the events they are handling.  Those methods will then log a meaningful message based on the data they are provided.  This allows us to separate what happened with how it is logged and provide a central location to edit what data and text goes into the logs.

I have created a Logging folder in each of the projects and a DomainEventSource and SiteEventSource class under the respective new folders.  You can visit the Enterprise Library site for a better understanding of how to use the library, but here is my basic implementation:

    public class DomainEventSource : EventSource
    {
        public class Keywords
        {
            public const EventKeywords Data = (EventKeywords)1;
            public const EventKeywords Diagnostic = (EventKeywords)2;
            public const EventKeywords Perf = (EventKeywords)4;
        }

        public class Tasks
        {
            public const EventTask Data = (EventTask)1;
        }

        private static readonly Lazy<DomainEventSource> Instance =
        new Lazy<DomainEventSource>(() => new DomainEventSource());

        private DomainEventSource() { }

        public static DomainEventSource Log { get { return Instance.Value; } }

        [Event(1, Message = "Application Failure: {0}",
        Level = EventLevel.Critical, Keywords = Keywords.Diagnostic)]
        public void Failure(string message)
        {
            this.WriteEvent(1, message);
        }

        [Event(2, Message = "Database Update Error: {0}",
        Level = EventLevel.Error, Keywords = Keywords.Diagnostic)]
        public void DbUpdateError(string message)
        {
            this.WriteEvent(2, message);
        }

        [Event(3, Message = "Database Validation Error: {0}",
        Level = EventLevel.Warning, Keywords = Keywords.Diagnostic)]
        public void DbValidationError(string message)
        {
            this.WriteEvent(3, message);
        }

    }

The SiteEventSource class is the same except without the Db...Error methods.

Now that the event source classes are setup, we need to tell the application what to do with logged events.  I have created a LoggingConfig class in the App_Start folder in the Site project:

    public class LoggingConfig
    {
        public static void RegisterLoggingSources(IEnumerable<EventSource> logSources, ISiteSettings settings)
        {
            var logListener = new ObservableEventListener();

            foreach (var logSource in logSources)
            {
                logListener.EnableEvents(
                  logSource, EventLevel.LogAlways, Keywords.All);
            }

            logListener.LogToFlatFile(
                    fileName: settings.LogFilePath,
                    formatter: new EventTextFormatter());
        }
    }

The RegisterLoggingSources method creates listeners for the event sources and will output log events to the file path specified in the ISiteSettings object.  We just need to call this method with the other config methods in the global.asax.cs class:

          LoggingConfig.RegisterLoggingSources(new List<EventSource> { SiteEventSource.Log, DomainEventSource.Log },???);

You will notice that the method requires an ISiteSettings object, which we have not configured yet.  We will get to that in step 7.

5. Exception Handling

I don't believe exception handling has been handled as well as it should be for basic .NET applications.  It's a difficult task to try to abstract exception handling, but it's a very obvious issue when there are so many duplicated try-catch blocks throughout projects that ultimately do the exact same thing.  Eventually Aspect Oriented Programming (AOP) should become more integrated with Object Oriented Programming (OOP) for cross cutting concerns such as exception handling.  One option that tries to solve exception handling with AOP in mind is PostSharp which uses attributes for exception handling.  Since PostSharp requires licensing and does some tricky things to actually implement this kind of error handling, we will not be using it for this demo.

Fortunately, while developing MVC and Web API, Microsoft realized the obvious issues with today's exception handling and provided some ways of abstracting it at the controller level.  We will be overriding the provided HandleErrorAttribute and creating a custom error handling attribute for both MVC and Web API that we will assign globally.  This, theoretically, will allow us to remove all error handling from within the controller methods (assuming you use good practice and keep most of the business logic out of the controller).  I have created a Filters folder under the Site project and added a MyNotebookHandleErrorAttribute class within that folder:

    public class MyNotebookHandleErrorAttribute : System.Web.Http.Filters.ExceptionFilterAttribute, System.Web.Mvc.IExceptionFilter
    {
        public void OnException(System.Web.Mvc.ExceptionContext filterContext)
        {
            SiteEventSource.Log.Failure(filterContext.Exception.Message);
        }

        public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext context)
        {
            SiteEventSource.Log.Failure(context.Exception.Message);
        }
    }

Since I am dealing with both MVC and Web API in this project, I am using this class to handle errors from both frameworks.  It might also be advantageous to add redirect logic in this attribute to send the client to an error page when it gets called.

To add this globally to the project for Web API, we will add it to the WebApiConfig.cs class in the App_Start folder:

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

This will replace the existing HandleErrorAttribute (config.Filters.Add(new HandleErrorAttribute())).  To add it globally to the project for MVC, we will add it to the FilterConfig class in the App_Start folder, similarly replacing the existing HandleErrorAttribute addition:

    filters.Add(new MyNotebookHandleErrorAttribute());

For the rest of the project, I will continue to utilize the typical try-catch error handling.

6. Implementing the Interfaces

Now that the basic interfaces and logging/exception handling classes are in place, we can begin the actual implementations of our interfaces.  I typically create Impl folders, short for Implementation, next to the interfaces to hold the implementations of the interfaces for logging, settings, and repositories.  For this demo I will only be creating one implementation of each, although it is beneficial to have more than one for things such as multiple environments and unit testing.

For the caching implementation I am using ASP.NET caching.  This is a very simple caching library which is why I am using it here, but it relies on the System.Web.dll library which is a very large library to reference just for caching.  It would more efficient to consider another library if possible, and, thankfully, abstracting caching into an interface will allow us to swap out implementations in the future with very little code changes.  You can find the actual implementation in the code linked to at the bottom of this posting.  I am also passing an IDomainSettings object into the constructor to handle default cache expiration times and whether or not caching is enabled.

The repository implementation takes a MyNotebookDbContext object and an ICacheProvider object as arguments in the constructor.  These are used for retrieving the necessary data objects.  The exact implementation can be found in the code linked to at the bottom of this posting.

The settings implementation will only be implemented in the Site project for this demo.  It may be beneficial to add a basic implementation to the Domain project, but since the Site project is the only project that uses it as a dependency right now, we are just going to implement it once.  I have created an ApplicationSettings class within a new Impl folder under the Settings folder in the Site project.  This class implements both of the settings interfaces and simply grabs the configurations from the AppSettings section of the web.config file:

    public class ApplicationSettings : ISiteSettings, IDomainSettings
    {
        public bool EnableCaching
        {
            get
            {
                bool enableCaching;
                if (bool.TryParse(ConfigurationManager.AppSettings["EnableCaching"], out enableCaching))
                    return enableCaching;

                return true;
            }
        }

        public int DefaultCacheExpirationInMin
        {
            get
            {
                int defaultCacheExInMin;
                if(int.TryParse(ConfigurationManager.AppSettings["EnableCaching"], out defaultCacheExInMin))
                    return defaultCacheExInMin;

                return 5;
            }
        }

        public string LogFilePath
        {
            get
            {
                return ConfigurationManager.AppSettings["LogFilePath"];
            }
        }
    }

As the application matures, it may be necessary to take the configurations from a different location.  Using the interface it will be easy to swap out the implementation in the future without affecting the dependent code.  Remember to add those settings to the <appsettings> section in the web config so that they can be accessed when the application runs.

7. IOC/Dependency Injection

Now that the basic implementations are in place, we need a way to tie those specific implementations to their corresponding interfaces and "inject" them throughout the project.  Using IOC, we are able to do just that.  For this project I am using Ninject as an IOC container and framework.  Unity is Microsoft's suggested framework, but it is a little too heavy on configuration (getting better with that now) and it doesn't have some useful functionalities that Ninject provides.

To setup Ninject we must first install the NuGet packages.  We will need two packages: Ninject MVC3 and Ninject.WebAPI.DependencyResolver.  The latter is used to provide an appropriate dependency resolver to be configured for Web API controllers.  Once these are installed into the Site project, you will notice a new class in the App_Start folder, NinjectWebCommon.  Within that folder we will need to make two modifications.  First we will setup the dependency resolver for the Web API controllers using the DependencyResolver NuGet package we just installed.  This is done by adding the following line in the CreateKernel() method just before the RegisterServices() call:

    GlobalConfiguration.Configuration.DependencyResolver =
            new NinjectDependencyResolver(kernel);

Then we will implement the RegisterServices() method using our new implementations and interfaces:

    kernel.Bind<MyNotebookDbContext>().ToSelf().InRequestScope();
    kernel.Bind<IDomainSettings>().To<ApplicationSettings>();
    kernel.Bind<ISiteSettings>().To<ApplicationSettings>();
    kernel.Bind<ICacheProvider>().To<AspNetCacheProvider>();
    kernel.Bind<INotesRepository>().To<NotesRepository>();

More details about ninject and how to bind dependencies can be found at the official Ninject website.  In short, we are telling Ninject to create one db context for every request, and then telling Ninject to provide an object of our implementation whenever one of the interfaces is needed.  Ninject will handle the lifecycle of those injected objects and provide them to object constructors as needed.

We also made a call to LoggingConfig earlier which we need to supply an ISiteSettings object for to get the appropriate log name.  Since that call is made in the Global.asax.cs class, we will need to manually retrieve the dependency instead of having it automatically injected.  To do this we will just need to add a static reference to the dependency resolver, which is the kernel for Ninject:

   public static IKernel Kernel
    {
        get
        {
            return bootstrapper.Kernel;
        }
    }

Now we can use this to get the dependency when calling the RegisterLoggingSources() method from the global.asax.cs class:

LoggingConfig.RegisterLoggingSources(new List<EventSource> { SiteEventSource.Log, DomainEventSource.Log },
                (ISiteSettings)NinjectWebCommon.Kernel.GetService(typeof(ISiteSettings)));

This is asking Ninject to give us an object that implements the ISiteSettings interface.  Ninject will realize that it is bound to our ApplicationSettings implementation and provide an object of that class.

8. Utilizing the Infrastructure

For finalizing the demo, we need to prove that our infrastructure works by using the Note data we "seeded" in the db initializer in a web page.  To do this, I will be creating a new NotesController and editing the home page to use that new controller to retrieve and display my notes.  Here is the new NotesController that uses the repository to get my notes (remember that Ninject will supply the arguments for the constructor automatically now that we bound our implementations):

    [RoutePrefix("api/Notes")]
    public class NotesController : ApiController
    {
        private INotesRepository _notesRepository;
        private ModelFactory _modelFactory;
        public NotesController(INotesRepository notesRepository, ModelFactory modelFactory)
        {
            _notesRepository = notesRepository;
            _modelFactory = modelFactory;
        }

        [Route("", Name="GetNotesByUserId")]
        public IEnumerable<NoteModel> GetUsersNotes()
        {
            return _notesRepository.GetUsersNotes(User.Identity.GetUserId()).Select(_modelFactory.Create);
        }
    }

You'll notice the Route attributes that I am using on the controller itself as well as the GetUsersNotes method.  I believe attribute routing is much more intuitive and flexible than the original style of routing in MVC and Web API.  I have also utilized the extension method Identity.GetUserId() from the Microsoft.AspNet.Identity package which allows me to restrict callers to only getting their own notes.  Finally, you'll notice that I am using a "ModelFactory" and "NotesModel" instead of just using the Note objects provided directly from the repository. This helps to separate the data access layer (DAL) from the view layer.  Using this abstraction we can add and remove data from the view without needing to change anything in the dependent Domain project.  You can find the ModelFactory class and the NoteModel class in the Models folder in the project linked to at the bottom of this post.

Now that the controller is setup, I will make a few small edits to retrieve and show the notes using Knockout.  In Scripts/App/app.datamodel.js I have added getNotesUrl = "/api/Notes" to the routes and created this method to retrieve the notes:

    self.getNotes = function () {
        return $.ajax(getNotesUrl, {
            type: "GET",
            headers: getSecurityHeaders()
        });
    };

In Scripts/App/home.viewmodel.js I have created a notes variable, bound it as an observable, and intialized it with data using the method I created above:

    // Private operations
    function initialize() {
        dataModel.getNotes().success(function (data) {
            self.notes(data);
        });
    }

    // Data
    self.notes = ko.observableArray();

    initialize();

Now I will add the bound data to the home page by replacing the current html in Views/Home/_Home.cshtml with:

<!-- ko with: home -->
<h2>My Notebook</h2>

<div class="row">
    <div class="col-md-10">
        <div data-bind="foreach: notes">
            <h3 data-bind="text: heading"></h3>
            <p data-bind="text: body"></p>
            <p style="color:#cecece">Created on <span data-bind="text: createdDate"></span></p>
        </div>
    </div>
</div>
<!-- /ko -->

Now you should be able to run the application, login with the seeded user "Admin" with the password "password" and see the following:

If you can see the login page but cannot login, you should verify that the db initializer has been setup properly and that the connection string in the web.config file is working (you can test that using the server explorer in Visual Studio).

 

Conclusion

This demo was meant to be a very basic foundation for any Web API and/or MVC application.  There is obviously a lot left to do to fully flush out a scalable application, but at least with this foundation you can start to customize the solution for your own needs.

As mentioned above, I have attached the solution here as a reference.  You will need to restore the packages through NuGet to get it to run.

Attachments

MyNotebook.zip