Wednesday 15 May 2013

AngularJS Tutorial and .Net Web API (part 2)

This post builds on the previous post about running the AngularJS tutorial application on top of the .Net Web API Framework.  All it's going to do is tidy things up a bit, introducing a repository for the controller to get the data from, rather than it having hard-coded JSON.  Along the way we'll use the DependencyResolver so that our controller isn't creating its own dependencies.

You should be able to see all the code by using 'git checkout -f step5b'.  Once again, you can either just read the code in its finished state, or work from scratch.

First let's set up our data source, copy the file 'app/phones/phones.json' from the AngularJS tutorial directory to the App_Data folder in the MVC project directory.

In the models directory create a new 'Phone.cs' class.  Because of the conventions used by the JSON serialization library, either the 'Models/Phone' class can use Camel case for its properties, or you can change the 'Scripts/AngularControllers/controller.js' file and the 'Views/Home/Index.cshtml' file so that the property names use Pascal case to match your model class.  No-one dies either way.  I've chosen to make the C# class match the AngularJS tutorial, because I only have to make the change in one place, the compiler can warn me of any problems if I use the wrong case somewhere else and I won't keep having to change the other files as I work through the AngularJS tutorial.

public class Phone
{
	public int age { get; set; }
	public string carrier { get; set; }
	public string id { get; set; }
	public string imageUrl { get; set; }
	public string name { get; set; }
	public string snippet { get; set; }
}


Create a data access folder at the root of the project and in it create an IPhoneRepository.cs file.  This should contain the IPhoneRepository interface that has a single method, GetAll()
namespace AngularJSTutorial.DataAccess
{
    public interface IPhoneRepository
    {
        IEnumerable<Phone> GetAll();
    }
}

In the same folder create a FileDrivenPhoneRepository class that implements IPhoneRepository.  This concrete implementation will read from the JSON file (our data store) in the App_Data.

using System;
using System.Collections.Generic;
using AngularJSTutorial.Models;
using Newtonsoft.Json;

namespace AngularJSTutorial.DataAccess
{
    public class FileDrivenPhoneRepository : IPhoneRepository
    {
        public IEnumerable<Phone> GetAll()
        {
            string dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
            var phonesText = System.IO.File.ReadAllText(dataDirectory +"/phones.json");
            return JsonConvert.DeserializeObject<List<Phone>>(phonesText);
        }
    }
}

Next amend the PhonesController so that its constructor has an IPhoneRepository as a parameter and the 'GetAll' method uses that repository to get it's list of phones:
 public class PhonesController : ApiController
    {
        private readonly IPhoneRepository phoneRepo;

        public PhonesController(IPhoneRepository phoneRepo)
        {
            this.phoneRepo = phoneRepo;
        }

        public IEnumerable<Phone> GetAll()
        {
            return this.phoneRepo.GetAll();
        }
    }
 
Make sure you add the relevant 'using' statements as well, or the project won't compile.

Now you just need to ensure that the IPhoneRepository dependency is injected.  Create a folder called 'Infrastructure' at the root level of the project and add a class to it called 'SimpleResolver' (or think of a good name).  This class will implement the IDependencyResolver and will do one thing only - it will construct the PhonesController class and manage its dependencies (well, 'dependency' in this case).
public class SimpleResolver : IDependencyResolver
    {
        public IDependencyScope BeginScope()
        {
            return this;
        }

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof (PhonesController))
            {
                var repo = new FileDrivenPhoneRepository();
                return new PhonesController(repo);
            }

            return null;
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return new List<object>();
        }

        public void Dispose()
        {
            
        }
    }


Finally we need to make sure that this is what the application uses when creating the PhonesController, so add the following line to the Application_Start() method of the Global.asax.cs file:
GlobalConfiguration.Configuration.DependencyResolver = new SimpleResolver();


Also ensure that you have the following 'using' statement at the top of the file:
using AngularJSTutorial.Infrastructure;


If you're familiar with creating your own ControllerFactory as part of a .Net MVC website, this probably feels pretty similar.

While we're now in the slightly odd position of deserializing a JSON file only to let the framework re-serialize it in order for AngularJS to use it, I think this represents a more usual scenario of how the framework can be used - most likely your application will sit on top of a database of some sort and your repository will pull out the required data from that.

It's worth noting the bit of magic that the framework performs in the PhonesController now, where it automatically serializes the objects to JSON.  This is because the Scripts/controller.js method 'PhoneListCtrl' calls $http.get(), which under the covers sets the Accept header of the request to 'application/json, text/plain, */*'.  When the Web API receives a request it interrogates the Accept header in order to decide what kind of formatter it should use to serialize data for the response and ultimately uses the Newtonsoft.Json library for the serialization to JSON.

No comments:

Post a Comment