Friday, March 9, 2012

WebApi - An Example with Database Persistance

As discussed in my last post the new Web Api feature which is packaged with the MVC4 beta rollout allows for the use of Inversion Of Control (Dependency Injection) similar to that of MVC3.

In this post I am going to step through a "real life" Web Api project called "Healthy Muscle Web", which will allow consumers of the API to interact with a set of data relating to workouts. Whether the consumers be mobile applications (iPhone, Android), mashups or other web platforms (Ruby, php, AspNet MVC).

Let's start with a data model


Code Snippet

    public class Workout
    {
        public virtual Guid Id { get; set; }
        public virtual string DateTime { get; set; }
        public virtual string WorkoutName { get; set; }
    }



For the persistence I will be using NHibernate. Now onto the controller implementation. From my understanding in REST, there are 4 primary verbs that we are concerned with:


  1. POST: Create a new entity

  2. PUT: Update an existing entity

  3. GET: Retrieve a specified entity

  4. DELETE: Delete a specified entity





Code Snippet

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using NHibernate.Linq;
using WebApi.Data.Configuration;
using WebApi.Data.Models;

namespace WebApi.Controllers
{
    public class WorkoutController : ApiController
    {
        private readonly IUnitOfWork _unitOfWork;

        public WorkoutController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        // GET /api/workouts
        [HttpGet]
        public IEnumerable<Workout> Get()
        {
            return _unitOfWork.CurrentSession.Query<Workout>().AsEnumerable();
        }

        // GET /api/workout/5
        [HttpGet]
        public Workout Get(Guid id)
        {
            var workout = _unitOfWork.CurrentSession.Query<Workout>().SingleOrDefault(x => x.Id == id);

            //If entity expected does not exist return 404.
            if (workout == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            return workout;
        }

        // POST /api/workouts
        [HttpPost]
        public HttpResponseMessage<Workout> Post(Workout workout)
        {
            var id = _unitOfWork.CurrentSession.Save(workout);
            _unitOfWork.Commit();

            var response = new HttpResponseMessage<Workout>
                (workout, HttpStatusCode.Created);
            response.Headers.Location = new Uri(Request.RequestUri,
                Url.Route(null, new { id }));

            return response;
        }

        // PUT /api/workouts
        [HttpPut]
        public Workout Put(Workout workout)
        {
            var existingWorkout = _unitOfWork.CurrentSession.Query<Workout>().SingleOrDefault(x => x.Id == workout.Id);

            //check to ensure update can occur
            if(existingWorkout==null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            //merge detached entity into session
            _unitOfWork.CurrentSession.Merge(workout);
            _unitOfWork.Commit();

            return workout;
        }

        // DELETE /api/workouts/5
        [HttpDelete]
        public HttpResponseMessage Delete(Guid id)
        {
            var existingWorkout = _unitOfWork.CurrentSession.Query<Workout>().SingleOrDefault(x => x.Id == id);

            //check to ensure delete can occur
            if(existingWorkout==null)
                return new HttpResponseMessage(HttpStatusCode.NoContent);

            _unitOfWork.CurrentSession.Delete(existingWorkout);

            _unitOfWork.Commit();

            return new HttpResponseMessage(HttpStatusCode.NoContent);
        }
    }
}



For some methods I am just returning an entity, in this case Workout, and in other instances I am returning a hopefully more informational HttpResponseMessage. For example, in the case of the Post of a new entity, I need to tell the REST Client the new location of the newly added product in the header. In other actions I am also throwing a HttpResponseException if the resource requested is not found as per the Get method.

The routing configuration in the Global.asax look like


Code Snippet

routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );



Looking at the WorkoutController there is a dependency on the interface IUnitOfWork


Code Snippet

    public interface IUnitOfWork : IDisposable
    {
        ISession CurrentSession { get; }
        void Commit();
        void Rollback();
    }



which has a concrete implementation of


Code Snippet

public class UnitOfWork : IUnitOfWork
    {
        private readonly ISessionFactory _sessionFactory;
        private readonly ITransaction _transaction;

        public UnitOfWork(ISessionFactory sessionFactory)
        {
            _sessionFactory = sessionFactory;
            CurrentSession = _sessionFactory.OpenSession();
            _transaction = CurrentSession.BeginTransaction();
        }

        public ISession CurrentSession { get; private set; }

        public void Dispose()
        {
            CurrentSession.Close();
            CurrentSession = null;
        }

        public void Commit()
        {
            if (_transaction.IsActive)
                _transaction.Commit();
        }

        public void Rollback()
        {
            _transaction.Rollback();
        }
    }



Nothing too out there at the moment (nor will there be)

I have two Structure Map Registries



Code Snippet

public class NHibernateRegistry : Registry
    {
        public NHibernateRegistry()
        {
            var cfg = new NHibernate.Cfg.Configuration()
                .SetProperty(Environment.ReleaseConnections, "on_close")
                .SetProperty(Environment.Dialect, typeof(MsSqlCe40Dialect).AssemblyQualifiedName)
                .SetProperty(Environment.ConnectionDriver, typeof(SqlServerCeDriver).AssemblyQualifiedName)
                .SetProperty(Environment.ConnectionStringName, "WebApi")
                .AddAssembly(typeof(Workout).Assembly);

            var sessionFactory = cfg.BuildSessionFactory();

            For<NHibernate.Cfg.Configuration>().Singleton().Use(cfg);

            For<ISessionFactory>().Singleton().Use(sessionFactory);

            
            For<ISession>().HybridHttpOrThreadLocalScoped()
                .Use(ctx => ctx.GetInstance<ISessionFactory>().OpenSession());

            For<IUnitOfWork>().HybridHttpOrThreadLocalScoped()
                .Use<UnitOfWork>();
        }
    }



and



Code Snippet

public class DomainRegistry : Registry
    {
        public DomainRegistry()
        {
            Scan(x =>
                     {
                         x.TheCallingAssembly();
                         x.WithDefaultConventions();
                     });
        }
    }



To bootstrap the IOC configuration I have a class



Code Snippet

public class DependencyRegistrar
    {
        protected static bool DependenciesRegistered;

        private static void RegisterDependencies()
        {
            ObjectFactory.Initialize(x => x.Scan(y =>
            {
                y.AssemblyContainingType<DomainRegistry>();
                y.AssemblyContainingType<NHibernateRegistry>();
                y.LookForRegistries();
            }));
        }

        private static readonly object Sync = new object();

        public void ConfigureOnStartup()
        {
            RegisterDependencies();
        }

        public static bool Registered(Type type)
        {
            EnsureDependenciesRegistered();
            return ObjectFactory.GetInstance(type) != null;
        }

        public static void EnsureDependenciesRegistered()
        {
            if (DependenciesRegistered) return;
            lock (Sync)
            {
                if (DependenciesRegistered) return;
                RegisterDependencies();
                DependenciesRegistered = true;
            }
        }
    }



The Global.asax is as follows



Code Snippet

protected void Application_Start()
        {
            new DependencyRegistrar().ConfigureOnStartup();

            var container = ObjectFactory.Container;

            GlobalConfiguration.Configuration.ServiceResolver.SetResolver(
                new StructureMapDependencyResolver(container));

            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            BundleTable.Bundles.RegisterTemplateBundles();
        }



EDIT: As Rob pointed out in the comments I'm not releasing the instances of IUnitOfWork so the disposing method is not being called, which could have an impact on the memory. Thanks Rob for the pick up. I've added the below code in the Global.asax which is called on the Appication_EndRequest event.


        protected void Application_EndRequest(object sender, EventArgs e) {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        }




The StructureMap configuration along with the Dependency Resolver (as discussed in my last post) allows for dependency injection.

You can see the full implementation at GitHub.com