Accessing a ASP.NET Rest Service from ASP.NET MVC

Note: All of the code for this example can be downloaded here.

One of the most exciting features in ASP.NET is the new Web API. To get started, here is the Web API service:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using WebAPITest.Models;

namespace WebAPITest.Controllers
{
 public class ProductsController : ApiController
 {
  public IEnumerable GetProducts()
  {
   return ProductsRepository.data;
  }

  public Product GetProduct(int id)
  {
   return ProductsRepository.get(id);
  }

  [HttpDelete]
  public void ResetProducts()
  {
   ProductsRepository.reset();
  }

  public void DeleteProduct(int id)
  {
   ProductsRepository.delete(id);
  }

  public void PutProduct(Product product)
  {
   ProductsRepository.update(product);
  }

  public void PostProduct(Product product)
  {
   ProductsRepository.add(product);
  }
 }
}

As you can see, the API controller is using a repository. Here is the code for the products repository:

using System;
using System.Collections.Generic;
using System.Linq;
using WebAPITest.Models;

namespace WebAPITest
{
	public class ProductsRepository
	{
		private static IEnumerable _data;

		public static void reset()
		{
			_data = null;
			var x = data;
		}

		public static void delete(int id)
		{
			var existingProduct = false;
			Product fetchedProduct;

			if (id == 0)
			{
				throw new
				Exception("The id passed must be non-zero.");
			}

			try
			{
				fetchedProduct = get(id);
				var data = ProductsRepository.data.ToList();
				data.Remove(fetchedProduct);
				ProductsRepository.data = data;
			}
			catch (Exception)
			{
				throw new
				Exception(@"The product you are
      attempting to delete does not exist");
			}
		}

		public static IEnumerable get()
		{
			return _data;
		}

		public static Product get(string name)
		{
			var product = _data.FirstOrDefault(x => x.Name == name);

			if (product != null)
			{
				return product;
			}
			else
			{
				throw new Exception();
			}
		}

		public static Product get(int id)
		{
			var product = _data.FirstOrDefault(x => x.Id == id);
			if (product != null)
			{
				return product;
			}
			else
			{
				throw new Exception();
			}
		}

		public static IEnumerable data
		{
			get
			{
				if (_data == null)
				{
					_data = new List
					{
						new Product() { Id = 1, Name = "Gizmo 1", Price = 1.99M },
						new Product() { Id = 2, Name = "Gizmo 2", Price = 2.99M },
						new Product() { Id = 3, Name = "Gizmo 3", Price = 3.99M }
					};
				}

				return _data;
			}
			set
			{
				_data = value;
			}
		}

		public static void add(Product product)
		{
			var existingProduct = false;
			Product fetchedProduct;

			if (product.Id != 0)
			{
				throw new
				Exception(@"Posted products cannot
      contain an id");
			}

			try
			{
				fetchedProduct = get(product.Name);
				existingProduct = true;
			}
			catch (Exception)
			{
				//this is actually good that we are here. Means we didn't
				//find an existing product so it is OK to insert;
				product.Id = ProductsRepository.data.Select(x => x.Id).Max() + 1;

				var data = ProductsRepository.data.ToList();
				data.Add(product);
				ProductsRepository.data = data;
			}

			if (existingProduct)
			{
				throw new
				Exception(@"The product name you are
      attempting to insert already exists.");
			}
		}

		public static void update(Product product)
		{
			try
			{
				Product existingProduct = get(product.Id);
				existingProduct.Name = product.Name;
				existingProduct.Price = product.Price;
			}
			catch (Exception)
			{
				throw new
				Exception(@"The product name you are
      attempting to put does not exist.");
			}
		}
	}
}

For testing purposes, the repository has an internal data store that nevertheless, can be manipulated. In actual practice, the Web API would sit over a database or perhaps another service.

The model for the service is very simple:

using System;
using System.Linq;

namespace WebAPITest.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

In order to support cross-site scripting, I added the following code to the Global.asax Application_Start():

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter());

This JsonMediaTypeFormatter can be found on GitHub: https://github.com/thinktecture/Thinktecture.Web.Http

Testing the API with fiddler, we get this with the following URL: localhost:18950/api/products:

With the Web API in place, the next step is to create an ASP.NET MVC Application to consume the Web API data.

The first step is to create a model in the MVC App:

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace MVCWebAPIConsumer.Models
{
 public class Product
 {
  public int Id {get; set;}
  [Required]
  public string Name {get; set;}
  [Required]
  public decimal Price {get; set;}
 }
}

Notice in this context, we are using data annotations to indicate which fields are required.

With a model in place, we need a controller:

using System;
using System.Linq;
using System.Web.Mvc;
using MVCWebAPIConsumer.Models;

namespace MVCWebAPIConsumer.Controllers
{
 public class ProductsController : Controller
 {
  //
  // GET: /Products/
  ProductRepository _productRepository;

  public ProductsController(ProductRepository productRepository)
  {
   _productRepository = productRepository;
  }

  public ActionResult Index()
  {
   return View(_productRepository.Get());
  }

  //
  // GET: /Products/Details/5

  public ActionResult Details(int id)
  {
   var product = _productRepository.Get(id);

   return View(product);
  }

  //
  // GET: /Products/Create

  public ActionResult Create()
  {
   return View(_productRepository.New());
  }

  //
  // POST: /Products/Create

  [HttpPost]
  public ActionResult Create(Product model)
  {
   try
   {
    _productRepository.Create(model);

    return RedirectToAction("Index");
   }
   catch
   {
    return View();
   }
  }

  //
  // GET: /Products/Edit/5

  public ActionResult Edit(int id)
  {
   return View(_productRepository.Get(id));
  }

  //
  // POST: /Products/Edit/5

  [HttpPost]
  public ActionResult Edit(Product model)
  {
   try
   {
    _productRepository.Update(model);

    return RedirectToAction("Index");
   }
   catch
   {
    return View();
   }
  }

  //
  // GET: /Products/Delete/5

  public ActionResult Delete(int id)
  {
   return View(_productRepository.Get(id));
  }

  //
  // POST: /Products/Delete/5

  [HttpPost]
  public ActionResult Delete(int id, FormCollection collection)
  {
   try
   {
    _productRepository.Delete(id);
    return RedirectToAction("Index");
   }
   catch
   {
    return View();
   }
  }
 }
}

Looks just like any other controller that uses a repository. Looking at the code, the repo could be communicating with any type of data source. The controller does not care. Let’s take a look at the ProductRepository:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using MVCWebAPIConsumer.Models;

namespace MVCWebAPIConsumer
{
 public class ProductRepository
 {
  private string restService = ConfigurationManager.AppSettings["restService"];
  private RequestMethod requestMethod = new RequestMethod();
  ISerialization _serializer;

  public ProductRepository(ISerialization serializer)
  {
   _serializer = serializer;
  }

  public Product New()
  {
   return new Product();
  }

  public void Update(Product product)
  {
   String json = serializeProduct(product);
   requestMethod.getRequest("PUT", "application/json", "http://localhost:18950/api/products", json).GetResponse();
  }

  public List Get()
  {
   var responseStream = requestMethod.GetResponseStream(requestMethod.getRequest("GET", "application/json",
    string.Format("{0}/api/products/", restService))
                                                                     .GetResponse());

   var products = deSerializeProduct<List>(responseStream) as List;

   return products;
  }

  public Product Get(int id)
  {
   var responseStream = requestMethod.GetResponseStream(requestMethod.getRequest("GET", "application/json",
    string.Format("{0}/api/products/{1}", restService, id)).GetResponse());

   var product = deSerializeProduct(responseStream) as Product;

   return product;
  }

  public void Create(Product product)
  {
   String json = serializeProduct(product);
   requestMethod.getRequest("POST", "application/json", string.Format("{0}/api/products/", json).GetResponse();
  }

  public object deSerializeProduct(Stream stream)
  {
   var retval = _serializer.DeSerialize(stream);

   return retval;
  }

  public string serializeProduct(Product product)
  {
   var retval = _serializer.Serialize(product);

   return retval;
  }

  public void Delete(int id)
  {
   requestMethod.GetResponseStream(requestMethod.getRequest("DELETE", "application/json",
    string.Format("{0}/api/products/{1}", restService, id)).GetResponse());
  }
 }
}

OK…now we see what’s going in with the repository. The process of dealing with the WebAPI services is very simple. When fetching a list of products, the necessary WebAPI method is invoked, which returns JSON. That JSON is de-serialized to:

List<Product>

When updating a product, the updated Product object is sent to the repository. That object is serialized to JSON and is then added to the body of a request that is passed to the WebAPI. To keep the process simple, I abstracted the httpwebrequest and httpwebresponse with helper methods. In addition, I created a helper method to unpack the response body.

An additional thing I did was abstract away the serializer. I’m using JSON.NET to handle the serialization. To make it easy to swap out the serializer, I constructed this interace:

using System;
using System.IO;
using System.Linq;

namespace MVCWebAPIConsumer
{
 public interface ISerialization
 {
   string Serialize(object o);
   object DeSerialize(Stream stream);
 }
}

Here’s the code for the JSON.NET Serializer:

using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;

namespace MVCWebAPIConsumer
{
 public class JsonNetSerialization : ISerialization
 {
  public string Serialize(object o)
  {
   return JsonConvert.SerializeObject((T)o);
  }

  public object DeSerialize(System.IO.Stream stream)
  {
   return JsonConvert.DeserializeObject(new StreamReader(stream).ReadToEnd());
  }
 }
}

If instead, you wish to use the System.Runtime.Serialization.Json object, you could use the following:

using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Json;

namespace MVCWebAPIConsumer
{
 public class DefaultSerialization : ISerialization
 {
  public string Serialize(object o)
  {
   String json;
   using (var stream = new MemoryStream())
   {
    var serializer = new DataContractJsonSerializer(typeof(T));
    serializer.WriteObject(stream, (T)o);
    json = Encoding.UTF8.GetString(stream.ToArray());
   }
   return json;
  }

  public object DeSerialize(System.IO.Stream stream)
  {
   var serializer = new DataContractJsonSerializer(typeof(T));
   return serializer.ReadObject(stream);
  }
 }
}

With a controller in place, we can build views in the same way we would for any ASP.NET MVC application. Once we have our views, all we need to to do is start the WebAPI and run the MVC app:

When we navigate to the home page, this is what we will see:

You can see all of the requests for the MVC App and the one request to the Web API.

If you attempt to save a product with an empty name – you will see the data annotations and unobtrustive JavaScript in action:

How about creating a product. Here’s what you will see:

The Create acton (POST) in the MVC application invokes the ProductRepository, passing the new product object. The ProductRepository in turn serializes that product object and then invokes a POST on the WebAPI. You can see this activity in the previous figure.

It really is that simple to incorporate the WebAPI into your application development efforts.

What about accessing the WebAPI from another domain? To illustrate, I created a simple page on another domain with the following JavaScript:

var url = 'http://localhost:18950/api/products?callback=?';

$.getJSON(url, function(data) {
	$("#products").html(createProductHtml(data));
})
.error(function(x,y,z) { });
function createProductHtml(data) {
   var items = [];
      $.each(data, function(key, val) {
	     items
		   .push('</pre>
<ul>
	<li id="' + val.Id + '">'
 + val.Name + ': ' + val.Price + '</li>
</ul>
<pre>
');
	  });

return $('', {
		html: items.join('')
	   })
}

To support the cross-domain script call,, callback=? was added to the url. Fiddler illustrates what the response looks like:

Reminder, this only works if you add the JsonpMediaTypeFormatter instance to the GlobalConfiguration.Configuration.Formatters in the Global.asax.

Here’s the output from the JavaScript:

I hope this sample helps you get up and running with the Web API!

ADDITION:

Apparently, I neglected to add the listing for the RequestMethod Class. Here it is:
Note: you will note there are places where the using statement should be used. I’ll get it!! :-)

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace MVCWebAPIConsumer
{
 public class RequestMethod
 {
  public WebRequest getRequest(string method, string contentType, string endPoint, string content)
  {
   var request = this.getRequest(method, contentType, endPoint);
   var dataArray = Encoding.UTF8.GetBytes(content.ToString());
   request.ContentLength = dataArray.Length;
   var requestStream = request.GetRequestStream();
   requestStream.Write(dataArray, 0, dataArray.Length);
   requestStream.Flush();
   requestStream.Close();

   return request;
  }

  public WebRequest getRequest(string method, string contentType, string endPoint)
  {
   var request = WebRequest.Create(endPoint);
   request.Method = method;
   request.ContentType = contentType;

   return request;
  }


  public Stream GetResponseStream(WebResponse response) {

   return response.GetResponseStream();
  
  }

  public StreamReader GetResponseReader(WebResponse response) {
   return new StreamReader(GetResponseStream(response));
  
  }

  public string unPackResponse(WebResponse response)
  {
   return GetResponseReader(response).ReadToEnd();
  }

 }
}

Another set of code that will be useful are the tests that drive the Web API. Here are those tests:

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using NUnit.Framework;

namespace WebAPITest
{
 [TestFixture]
 public class tests
 {
  [Test]
  public void TestPostProductXML()
  {
   HttpWebResponse response;
   var newContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Product xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Id>0</Id><Name>Gizmo 4</Name><Price>99.99</Price></Product>";
   var expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Product xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Id>4</Id><Name>Gizmo 4</Name><Price>99.99</Price></Product>";

   var success = false;

   //verify the product to be added does not already exist.
   try
   {
    response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/4").GetResponse() as HttpWebResponse;
   }
   catch (Exception)
   {
    success = true;
   }

   Assert.IsTrue(success);

   //add the new product
   response = this.getRequest("POST", "application/xml", "http://localhost:18950/api/products", newContent).GetResponse() as HttpWebResponse;

   //verify it was added

   response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/4").GetResponse() as HttpWebResponse;

   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);

   this.reset();
  }
        
  [Test]
  public void TestPostProductJSON()
  {
   HttpWebResponse response;
   var newContent = "{\"Id\":0,\"Name\":\"Gizmo 4\",\"Price\":99.99}";
   var expected = "{\"Id\":4,\"Name\":\"Gizmo 4\",\"Price\":99.99}";
   var success = false;

   //verify the product to be added does not already exist.
   try
   {
    response = this.getRequest("GET", 
      "application/json", 
      "http://localhost:18950/api/products/4").GetResponse() 
      as HttpWebResponse;
   }
   catch (Exception)
   {
    success = true;
   }
            
   Assert.IsTrue(success);

   //add the new product
   response = this.getRequest("POST", 
      "application/json", 
      "http://localhost:18950/api/products", 
      newContent)
                  .GetResponse() as HttpWebResponse;
           
   //verify it was added

   response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/4").GetResponse() as HttpWebResponse;

   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);

   this.reset();
  }

  [Test]
  public void TestDeleteProduct()
  {
   var response = this.getRequest("DELETE", "application/json", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   var success = false;
   try
   {
    response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   }
   catch (Exception)
   {
    success = true;
   }

   Assert.IsTrue(success);

   this.reset();
  }
        
  [Test]
  public void TestPutProductXML()
  {
   var oldContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Product xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Id>1</Id><Name>Gizmo 1</Name><Price>1.99</Price></Product>";
   var newContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Product xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Id>1</Id><Name>The New Title</Name><Price>99.99</Price></Product>";
   //Verify the existing state of product 1

   var response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   var actual = this.unPackResponse(response);
   Assert.AreEqual(oldContent, actual);

   //update product 1
   response = this.getRequest("PUT", "application/xml", "http://localhost:18950/api/products", newContent).GetResponse() as HttpWebResponse;

   //verify status code
   Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

   //Verify product 1 has been updated.
   response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   actual = this.unPackResponse(response);

   Assert.AreEqual(newContent, actual);

   this.reset();
  }
        
  [Test]
  public void TestPutProductJSON()
  {
   var oldContent = "{\"Id\":1,\"Name\":\"Gizmo 1\",\"Price\":1.99}";
   var newContent = "{\"Id\":1,\"Name\":\"The New Title\",\"Price\":99.99}";
            
   //Verify the existing state of product 1
   var response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   var actual = this.unPackResponse(response);
   Assert.AreEqual(oldContent, actual);

   //update product 1
   response = this.getRequest("PUT", "application/json", "http://localhost:18950/api/products", newContent).GetResponse() as HttpWebResponse;
            
   //verify status code
   Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

   //Verify product 1 has been updated.
   response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/1").GetResponse() as HttpWebResponse;
   actual = this.unPackResponse(response);

   Assert.AreEqual(newContent, actual);

   this.reset();
  }

  [Test]
  public void TestGetProductJSON()
  {
   var expected = "{\"Id\":1,\"Name\":\"Gizmo 1\",\"Price\":1.99}";

   var response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/1").GetResponse();
   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);
  }

  [Test]
  public void TestGetProductXML()
  {
   var expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Product xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Id>1</Id><Name>Gizmo 1</Name><Price>1.99</Price></Product>";
            
   var response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/1").GetResponse();
   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);
  }

  [Test]
  public void TestGetProductsXML()
  {
   var expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?><ArrayOfProduct xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Product><Id>1</Id><Name>Gizmo 1</Name><Price>1.99</Price></Product><Product><Id>2</Id><Name>Gizmo 2</Name><Price>2.99</Price></Product><Product><Id>3</Id><Name>Gizmo 3</Name><Price>3.99</Price></Product></ArrayOfProduct>";

   var response = this.getRequest("GET", "application/xml", "http://localhost:18950/api/products/").GetResponse();
   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);
  }

  [Test]
  public void TestGetProductsJSON()
  {
   var expected = "[{\"Id\":1,\"Name\":\"Gizmo 1\",\"Price\":1.99},{\"Id\":2,\"Name\":\"Gizmo 2\",\"Price\":2.99},{\"Id\":3,\"Name\":\"Gizmo 3\",\"Price\":3.99}]";

   var response = this.getRequest("GET", "application/json", "http://localhost:18950/api/products/").GetResponse();
   var actual = this.unPackResponse(response);

   Assert.AreEqual(expected, actual);
  }

  string unPackResponse(WebResponse response)
  {
   var dataStream = response.GetResponseStream();
   var reader = new StreamReader(dataStream);
   return reader.ReadToEnd();
  }

  WebRequest getRequest(string method, string contentType, string endPoint, string content)
  {
   var request = this.getRequest(method, contentType, endPoint);
   var dataArray = Encoding.UTF8.GetBytes(content.ToString());
   request.ContentLength = dataArray.Length;
   var requestStream = request.GetRequestStream();
   requestStream.Write(dataArray, 0, dataArray.Length);
   requestStream.Flush();
   requestStream.Close();

   return request;
  }
        
  WebRequest getRequest(string method, string contentType, string endPoint)
  {
   var request = WebRequest.Create(endPoint);
   request.Method = method;
   request.ContentType = contentType;

   return request;
  }

  private void reset()
  {
   this.getRequest("DELETE", "application/json", "http://localhost:18950/api/products").GetResponse();
  }
 }
}

About johnvpetersen

I've been developing software for 20 years, starting with dBase, Clipper and FoxBase + thereafter, migrating to FoxPro and Visual FoxPro and Visual Basic. Other areas of concentration include Oracle and SQL Server - versions 6-2008. From 1995 to 2001, I was a Microsoft Visual FoxPro MVP. Today, my emphasis is on ASP MVC .NET applications. I am a current Microsoft ASP .NET MVP. Publishing In 1999, I wrote the definitive whitepaper on ADO for VFP Developers. In 2002, I wrote the Absolute Beginner’s Guide to Databases for Que Publishing. I was a co-author of Visual FoxPro Enterprise Development from Prima Publishing with Rod Paddock, Ron Talmadge and Eric Ranft. I was also a co-author of Visual Basic Web Development from Prima Publishing with Rod Paddock and Richard Campbell. Education - B.S Business Administration – Mansfield University - M.B.A. – Information Systems – Saint Joseph’s University - J.D. – Rutgers University School of Law (Camden) In 2004, I graduated from the Rutgers University School of Law with a Juris Doctor Degree. I passed the Pennsylvania and New Jersey Bar exams and was in private practice for several years – concentrating transactional and general business law (contracts, copyrights, trademarks, independent contractor agreements, NDA’s, intellectual property and mergers and acquisitions.).
This entry was posted in ASP.NET MVC 4, oData, WebAPI. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Pingback: MVC | qujck

  • Ragesh P.R

    Wonderfull, Please give a Service Stack example with ASP.NET MVC

  • RageshS

    Great Post. Thanks you very much….

  • Pingback: friday links 23 « A Programmer with Microsoft tools

  • http://www.dotnetjalps.com/ Jalpesh Vadgama

    Nice information!! Thanks for sharing it!!

  • Anonymous

    OK…Check out the latest post. Look forward to your comments.

  • Anonymous

    LOL It’s great that you brought up that question!! I’m just putting the finishing touches on a class that abstracts HttpClient. Indeed, HttpClient provides a much cleaner way to do this work. Definitely makes serialization easier!!

    Stay tuned, it will be discussed in the next post.

  • Jesse Williamson

    If using Web API, why not use HTTPClient to consume the services?