Create your own music store with the 7digital music api



I should firstly disclose that I'm a developer at 7digital, now let's get started. Firstly you'll need a 7digital API Key, sign up fill out the form and you should get it shortly.

Before we begin

You'll need Visual Studio 2010 / 2012 or Visual Studio Express Web Edition. We'll be relying on .Net 4.0 so be sure that you have that working (should be fine if you have vs2012).

You can find all the code for this walkthrough on GitHub

The Video (written walkthrough is below)

Step 1 - Create a new Web Project

File -> New -> Project -> Web -> Asp.Net MVC 4 Web Application

I'm going to call my Music Store 'Favourite Wedding Songs'

Step 2 - Got the Key, got the secret

Take the consumer key and consumer secret which you should hopefully have received by email and put them in your web.config 

Step 3 - Install the 7digital API Wrapper from Nuget

Rather helpfully there's a 7digital API Wrapper on Nuget if we right click on references and click 'Manage Packages'. Then just search for 'sevendigital' you should find it and install away.

Step 4 - Creating an ApiUrl Class

Ok now before we dive in and create controllers and views for our web application we need a few basic bits of configuration code for our application. The 7digital API wrapper needs to know the API url which it expects to find by asking a class in a project which implements the IApiUrl interface. so I'd recommend creating a class much like that below. I'm hard-coding the urls in but obviously they're probably better off in config.

using SevenDigital.Api.Wrapper;

namespace FavouriteWeddingSongs.Models
{
	public class ApiUri : IApiUri
	{
		public string Uri
		{
			get { return "http://api.7digital.com/1.2"; }
		}

		public string SecureUri
		{
			get { return "https://api.7digital.com/1.2"; }
		}
	}
}

 

Step 5 - Creating an OAuth Credentials

Remember how we put the Key and Secret in our web.config, well we need a class that exposes that config to the API Wrapper. As before it reflects over our code and looks for a class that implements the IOAuthCredentials interface. So as you can see in the code below we simply wrap up access to those settings stored in our web.config.

using System.Configuration;
using SevenDigital.Api.Wrapper;

namespace FavouriteWeddingSongs.Models
{
	public class AppSettingsCredentials : IOAuthCredentials
	{
		public AppSettingsCredentials()
		{
			ConsumerKey = ConfigurationManager.AppSettings["Wrapper.ConsumerKey"];
			ConsumerSecret = ConfigurationManager.AppSettings["Wrapper.ConsumerSecret"];
		}

		public string ConsumerKey { get; set; }
		public string ConsumerSecret { get; set; }
	}
}

 

Step 6 - Creating a MusicController and View

So this is where the real fun begins, so lets start by creating a MusicController that will have 2 action methods. The first will be the boring Index action which will initially do absolutely nothing but display an Index View, which will house a simple search box. When you click on Search we'll post (well actually GET) a SearchResults page which will take the search query and perform a TrackSearch using the 7digital API Wrapper. Below you'll find some simple example code for our MusicController.

using System.Web.Mvc;
using SevenDigital.Api.Schema.TrackEndpoint;
using SevenDigital.Api.Wrapper;

namespace FavouriteWeddingSongs.Controllers
{
    public class MusicController : Controller
    {
        //
        // GET: /Music/

        public ActionResult Index()
        {			
            return View();
        }

	[AcceptVerbs(HttpVerbs.Get)] 
	public ActionResult SearchResults(string q)
	{
		var results = Api<TrackSearch>
			.Create
			.WithQuery(q)
			.WithParameter("imageSize", "200")
			.Please();
		return View(results);
	}
    }
}

Right so ignore the Index() action for a second and look at the SearchResults(string q) action. Here we see the ApiWrapper in action, as you can see it gives us a really nice fluent interface where we specify the type of operation we want to perform. In this case it's a Api<TrackSearch> we .Create a request we give it a .WithQuery(q) q being the input parameter that comes from the form on the Index actions view. We use an optional parameter that lets us specify the width in pixels of the album art we want returned in the result set. Then finallly as many were taught by our parents we say .Please(); and the API Wrapper converts our query into a HTTP request to the 7digital track search endpoint. What it ultimately looks like will be something like, 

http://api.7digital.com/1.2/track/search?q=Bruno Mars&oauth_consumer_key=YOUR_KEY_HERE&country=GB&pagesize=2&imageSize=200

The above request will return a response in XML, which will look something like the example response in the track search documentation but handily for us the API Wrapper will deserialize this into a .Net type for us, We then just give the result set to our view to build our web page.

Step 7 - Building an amazing user experience

So firstly using Google as our inspiration we'll use the below code as our Index View

 

@model dynamic

@{
	Layout = null;
}




	
		My Music Store
	
	
		
@using(@Html.BeginForm("SearchResults","Music",FormMethod.Get)) { }

Then we also need to work on the SearchResults. So have a look at the example below*

@model SevenDigital.Api.Schema.TrackEndpoint.TrackSearch

@{
	Layout = null;
}




	Search Results


	
@foreach (var item in Model.Results) {
cover art

@item.Track.Title

Your browser doesn't support the HTML5 audio tag. Buy this Track on 7digital for @item.Track.Price.FormattedPrice
}

*this is probably amongst the worst html ever, but you get the idea.

Note the track preview url. This can more formally be obtained using the preview api. But for our purposes this will probably suffice for now.

You'll also note that when this renders out our buy link will include you magic affiliateid on the end of the URL. This is the code that enables 7digital to track how many sales people you've referred have made.

Google would be proud of our extremely minimalist Index View.

Not so sure they'd like our results page, but we have used the funky html5 audio tag :)

Conclusion

We've barely scratched the surface of what is possible with the 7digital API so be sure to keep paying with the API and let us know what you come up with.

Thanks 7digital dev team.

 

 

 

 


Testing NancyFx



Hi, I've recently been getting quite into NancyFx. I've written a quickstart NancyFx tutorial if you've interested and even a NancyFx screencast.

Next I thought I'd do a very short followup on testing in NancyFx.

Before you start

Download Nancy Tutorial with testing source code from GitHub

Step 1 - Create a clean NancyBootstrapper

In the previous NancyFx tutorial, I hastily advocated overriding the ApplicationStartup and subscribing to the Error pipeline. Unfortunately this was also mixed in with the StructureMap Bootstrapper. So to seperate those concerns and make it so we can easily inject a Stub NancyBootstrapper.

Your new NancyBootstrapper should look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Nancy;
using Nancy.Bootstrappers.StructureMap;

namespace NancyTutorial
{
    public class NancyBootstrapper : StructureMapNancyBootstrapper
    {
        protected override void ConfigureApplicationContainer(StructureMap.IContainer existingContainer)
        {
            StructureMapContainer.Configure(existingContainer);
        }

    }
}

Step 2 - Create a class that implements IApplicationStartup

Next we're going to create a CarNotFoundExceptionErrorPipeline this class will simply hook the error pipeline and and some code to check whether the exception is of type CarNotFound and if so it'll return a 404 Not Found Error.

It should look like this:

using System.Text;
using Nancy;
using Nancy.Bootstrapper;

namespace NancyTutorial
{
    public class CarNotFoundExceptionErrorPipeline : IApplicationStartup
    {
        public void Initialize(IPipelines pipelines)
        {
            pipelines.OnError += (context, exception) =>
            {
                if (exception is CarNotFoundException)
                    return new Response
                    {
                        StatusCode = HttpStatusCode.NotFound,
                        ContentType = "text/html",
                        Contents = (stream) =>
                        {
                            var errorMessage =
                                Encoding.UTF8.GetBytes(
                                    exception.Message);
                            stream.Write(errorMessage, 0,
                                         errorMessage.Length);
                        }
                    };

                return HttpStatusCode.InternalServerError;
            };
        }
    }
}

Step 3 - Create a Tests Project

Now we simply need to create a Test project class library. Mines very originally called NancyTutorial.Web.Tests

NancyFx comes with a really nice, simple and best of all fast module called Nancy.Testing this does away with a lot of the need to use WebClient or WebRequest. Which makes life considerably easier, hopefully it'll help make your testing easier to read and thus more maintainable.

Step 4 - Create a simple Test using Nancy.Testing

Since we've only got 1 module in our project called CarModule I'm going to follow the lead and call the test class CarModuleTests. As the Nuget picture above shows we'll also be using trusty old NUnit and Moq to help us with testing our module.

Our first test will exercise the /car/123 endpoint. When we call it we'll expect back an Xml representation of a car, because we will explicity tell the server via the accept headers that we want application/xml. Nancy.Testing makes this really easy for us.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using Nancy;
using Nancy.Testing;

namespace NancyTutorial.Web.Tests
{
    [TestFixture]
    public class CarModuleTests
    {
        [Test]
        public void Should_return_car_as_xml_when_present()
        {
            //Given
            var mockRepository = new Mock<ICarRepository>();
            mockRepository.Setup(x => x.GetById(123)).Returns(new Car
                                                                  {
                                                                      Id = 123,
                                                                      Make = "Tesla",
                                                                      Model = "Model S"
                                                                  });           
            var browser = new Browser((c) => c.Module<CarModule>()
                                                 .Dependency<ICarRepository>(mockRepository.Object));
            //When
            var response = browser.Get("/car/123", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });
            var responseBody = response.BodyAsXml();
            //Then
            Assert.That(response,Is.Not.Null);
            
            string make = responseBody.Element("Car").Element("Make").Value;

            Assert.That(make,Is.EqualTo("Tesla"));
        }

      
    }
}

Step 5 - Mock the repository

So if we look at the code above, we firstly decide to mock an ICarRepository this is because we want to control what the repository hands back since we don't want this test talking to a real database (Not that this example even has one). We setup an expectation that the repository will have it's GetCarById(123) method called with an id of 123 and as you can see we hard code the return of a Tesla Model S with Id of 123.

Step 6 - Crete a Nancy testing Browser

Now this is where it gets cool. We create a Browser type (which is from Nancy testing and we pass it a lambda that is equivilent to a ConfigurableBootstrapper. All we do is for the CarModule with it's dependency on ICarRepository that we use a mock repository instead of the real one which would be resovled had we used the "real" NancyBootsrapper class.

Step 7 - call browser.Get()

Looking at the code again, you can see we call the browser.Get("/url") method. Nancy testing of course has all the relevant verbs such as browser.Post and browser.Delete. The second parameter is the interesting bit. It's another lambda. It lets us control properties of the requesting Nancy browser. For example above you see we've set this request to be over Http via the with.HttpRequest()  and helpfully we've hinted to the server that we can accept application/xml and we therefore will expect a response in xml.

There's another really helpful one call with.Query() which is how you put items on the querystring. Don't try just putting them in the get, it won't work, because that's todo with Nancy routing as far as Nancy testings concerned.

Step 8 - Assert away

We can also see that there's a multitude of handy extension methods such as the used above response.BodyAsXml() which takes the body response and gives us an XDocument in return.

Step 9 - Testing the response when we throw an exception

Next we'll write a simple test to check that, the above code whereby we moved the error handling code into a new class called CarNotFoundExceptionErrorPipeline is actually working.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using Nancy;
using Nancy.Testing;

namespace NancyTutorial.Web.Tests
{
    [TestFixture]
    public class CarModuleTests
    {
        [Test]
        public void Should_return_car_as_xml_when_present()
        {
            //Given
            var mockRepository = new Mock();
            mockRepository.Setup(x => x.GetById(123)).Returns(new Car
                                                                  {
                                                                      Id = 123,
                                                                      Make = "Tesla",
                                                                      Model = "Model S"
                                                                  });           
            var browser = new Browser((c) => c.Module()
                                                 .Dependency(mockRepository.Object));
            //When
            var response = browser.Get("/car/123", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });
            var responseBody = response.BodyAsXml();
            //Then
            Assert.That(response,Is.Not.Null);
            
            string make = responseBody.Element("Car").Element("Make").Value;

            Assert.That(make,Is.EqualTo("Tesla"));
        }

        [Test]
        public void Should_return_404_NotFound_when_CarRespository_throws_NotFoundException()
        {
            //Given
            var mockRepository = new Mock();
            mockRepository.Setup(x => x.GetById(111)).Throws(new CarNotFoundException("Car with Id 111 Not Found"));

            var browser = new Browser((c) => c.Module<CarModule>()
                                                 .Dependency<ICarRepository>(mockRepository.Object));
            //When            
            var response = browser.Get("/car/111", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });

            var responseBody = response.Body.AsString();
            //Then
            Assert.That(response.StatusCode,Is.EqualTo(HttpStatusCode.NotFound));
            Assert.That(responseBody, Is.EqualTo("Car with Id 111 Not Found"));



        }
    }
}

If we look at the 2nd test listed above  Should_return_404_NotFound_when_CarRespository_throws_NotFoundException() we can see that it's much the same as the first but with some simple modifications.

Firstly instead of returning a car we hard code the repository to throw a CarNotFoundException we then use the ConfigurableNancyBootsrapper lambda to resolve the CarModule dependency on ICarRepository to our mocked repository. combined with our mocked throwing of the exception we call Get on the enpoint.

Not we use another handy extension method response.Body.AsString() to convert the response into a simple string. We then assert that the code is correctly functioning by checking the http status code is indeed 404 Not Found and that the error message is correct.

Conclusion

That's it for a brief introduction to Nancy testing. As you can see it's really ncie and simple.

References

I highly recommend checking out the Nancy Testing Documentation for a more Nancy testing examples.

Thanks for reading Dan


Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2013