Tuesday, July 13, 2010

Data Driven Win Phone 7 Apps With MVC 2 JSON Services




I've been making a lot of little apps for Win Phone 7 in the past couple of weeks and I want to share a little bit of my code for other people who might be starting out and looking for some examples of creating a data driven Windows Phone 7 app.


We are going to be making a simple little product search for a fake company. We are going to show POSTing information to an MVC Controller action from our Win Phone 7 app, and we are going to show parsing a JSON response received from our MVC site and displaying it on the screen.


MVC 2 Website



The first thing I do when I'm creating a strictly service oriented site is to modify the Site.Master page to not display the login control, or the About page. I also, exclude the Account LogOn, Register and ChangePassword views so no one is tempted to register for the site. This step is optional, you can leave all of those things in, and you probably will have to if you have an existing MVC site you are altering.


Next, I create a new controller to serve my data. For our example we will call it ProductController and it will look something like this.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace WinPhone7.MVCExample.Website.Controllers
{
public class ProductInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}

public interface IProductService
{
IQueryable<ProductInfo> GetProducts(Predicate<ProductInfo> filter);
void AddProduct(ProductInfo newProduct);
}

// This is dirty... but it's a demo
public static class ProductRepository
{
// An In-Memory persistable list of products.
public static List<ProductInfo> Products = new List<ProductInfo>()
{
new ProductInfo()
{
Id = 1,
Name = "Product 1",
Description = "Some sample product that is related to toys"
},
new ProductInfo()
{
Id = 2,
Name = "Product 2",
Description = "Some sample product that is related to sports"
},
new ProductInfo()
{
Id = 3,
Name = "Product 3",
Description = "Some sample product that is related to cars"
},
new ProductInfo()
{
Id = 4,
Name = "Product 4",
Description = "Some sample product that is related to boats"
},
new ProductInfo()
{
Id = 5,
Name = "Product 5",
Description = "Some sample product that is related to tools"
},
new ProductInfo()
{
Id = 6,
Name = "Product 6",
Description = "Some sample product that is related to technology"
},
};
}

public class MockProductService : IProductService
{
public IQueryable<ProductInfo> GetProducts(Predicate<ProductInfo> filter)
{
return ProductRepository.Products
.Where(prod => filter(prod))
.AsQueryable();
}

public void AddProduct(ProductInfo newProduct)
{
newProduct.Id = ProductRepository.Products.Count + 1;
ProductRepository.Products.Add(newProduct);
}
}

public class ProductSearchRequest
{
public string SearchTerm { get; set; }
}

public class ProductListResponse
{
public List<ProductInfo> Matches { get; set; }
}

public class ProductSearchResponse : ProductListResponse
{ }

public class ProductAddResponse
{
public bool Success { get; set; }
public int Id { get; set; }
}

public class ProductController : Controller
{
IProductService _products;

public ProductController()
: this(new MockProductService()) // Lazy man's dependency injection.
{

}

public ProductController(IProductService productServ)
{
_products = productServ;
}

public ActionResult All()
{
// Get all products by passing true predicate.
var allProducts = _products.GetProducts(prod => true);

var response = new ProductListResponse()
{
Matches = allProducts.ToList()
};

return new JsonResult()
{
// Make sure we allow get's
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
// Set the data to our response.
Data = response
};
}

public ActionResult Search(ProductSearchRequest request)
{
var termLowered = request.SearchTerm.ToLower();
var matches = _products
.GetProducts(prod =>
prod.Name.ToLower().Contains(termLowered)
|| prod.Description.ToLower().Contains(termLowered));

var response = new ProductSearchResponse()
{
Matches = matches.ToList()
};

return new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = response
};
}

[HttpPost]
public ActionResult Add(ProductInfo product)
{
ProductAddResponse response;
if (!String.IsNullOrEmpty(product.Name)
&& !String.IsNullOrEmpty(product.Description))
{
_products.AddProduct(product);

response = new ProductAddResponse()
{
Success = true,
Id = product.Id
};
}
else
response = new ProductAddResponse()
{
Success = false,
Id = -1
};

return new JsonResult()
{
Data = response
};
}
}


I've included the response and request classes in the code example for brevity, but I usually separate these out into files in the models folder. Once we have our products controller in place, we can go ahead and run the website and see some results. Hit F5 to run the MVC Website, then navigate to /Product/All to see your example JSON response (I highly recommend Google Chrome and this JSON Content Viewer, makes the JSON all color coded and nice).


Win Phone 7 Services



Next, I go ahead and create my Services project as a Win Phone 7 class library. Now, I've written some helper classes to deal with requesting web content and parsing JSON results. I've included it in the Sample Code as a library called Service4u2. The meat and potatoes of this library is the BaseJsonService class. This will save you tremendous amounts of work for consuming JSON services. Here is what a basic Product Search JSON Service looks like (I've included out specific app's URL Helper for brevity).



using System.Collections.Generic;
using Service4u2.Json;

namespace WinPhone7.MVCExample.Client.Services
{
// Extra stuff included for brevity.
public class ProductInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}

public class ProductSearchRequest
{
public string SearchTerm { get; set; }
}

public class ProductListResponse
{
public List<ProductInfo> Matches { get; set; }
}

public class ProductSearchResponse : ProductListResponse
{ }

public class ProductAddResponse
{
public bool Success { get; set; }
public int Id { get; set; }
}

public static class ExampleUrlHelper
{
// Singleton pattern url helper.

private static ServiceUrlHelper _helper =
new ServiceUrlHelper("http://localhost.:49709");

public static ServiceUrlHelper Instance { get { return _helper; } }
}

// Product List Service.
public class ProductListService : BaseJsonService<ProductListResponse>
{
public void GetProductListAsync()
{
var url = ExampleUrlHelper
.Instance
.GetControllerActionParameterUrl("Product", "All", string.Empty);

StartServiceCall(url);
}
}

// Product Search Service.
public class ProductSearchService : BaseJsonService<ProductSearchResponse>
{
// We have to wrap our request so it is URL encoded properly for MVC.
// Our Product/Search action expects a parameter named request.
public class SearchRequestWrapper
{
public ProductSearchRequest request { get; set; }
}

public void SearchProductsAsync(ProductSearchRequest searchRequest)
{
var url = ExampleUrlHelper
.Instance
.GetControllerActionParameterUrl("Product", "Search", string.Empty);

var postData = new SearchRequestWrapper()
{
request = searchRequest
}.Postify(); // Postify our data, e.g. request.SearchTerm=blah&foo=1

StartServiceCall(url, HttpMethod.GET, postData);
}
}

// Product Add Service.
public class ProductAddService : BaseJsonService<ProductAddResponse>
{
// We have to wrap our request so it is URL encoded properly for MVC.
public class ProductAddWrapper
{
public ProductInfo product { get; set; }
}

public void AddProductAsync(ProductInfo productToAdd)
{
var url = ExampleUrlHelper
.Instance
.GetControllerActionParameterUrl("Product", "Add", string.Empty);

var postData = new ProductAddWrapper()
{
product = productToAdd
}.Postify();

StartServiceCall(url, HttpMethod.POST, postData);
}
}
}


Notice I've copied and pasted the request and response classes from the MVC project. This is a possible down-side to doing services this way. You won't get the autogenerated code that you get from a WCF service, but on the other hand, you will be better prepared in the long run to consume services from other websites that don't expose WCF implementations.


What we are doing is just preparing a URL and then making a request. The actual parsing of the JSON and converting it to our Result class is done by a DataContractJSONSerializer in the BaseJsonService class. To handle the result, or an error, just subscribe to the relevant events in your service consumer.


The Win Phone 7 App



The problem with the win phone 7 app example code is that everyone has different ways of doing things with WPF. I've put together a little MVVM framework tailored to the Win Phone 7 stuff and have included it with the example source code. I'll skip all the XAML and ViewModel code, because it's pretty standard MVVM stuff. Take a look at this demo video to get a look at how our app looks so far.




Hopefully, you've gotten pretty comfortable using MVC and JSON services with your Win Phone 7 apps. Feel free to use any of the code in this example. The Service4u2 and MVVM4u2 libraries are open source under the Microsoft Public License (Ms-PL).







Now Playing: Counting Crows - Friend of the Devil

Win Phone 7 "Words" Game - Mo Words


My wife and I have been playing a lot of word games lately on our IPhones. I've also been playing around with the Win Phone 7 SDK and I've been looking for a good project to work with. So, as a result, I made a little "Words" game called Mo Words.


I've just recently got it to a point where I could play a whole game. But there is still some work to be done for the play notifications (I'm hoping to get Push notifications working this weekend).


The game uses an MVC 2 website returning JSON responses as it's service layer. I hope to put together a couple posts on how I make data driven win phone apps with MVC 2 websites, including how to authenticate to an MVC 2 site and all that.


So, without further ado, here is a demo video (sorry it's choppy, the loading animations and dragging don't match the smoothness of the actual game, it's much better in person) and some screen shots.












Now Playing: Animal Collective - My Girls

Monday, July 12, 2010

FatSecret Sharp - C# Nutrition and Exercise Tracking Services


I started looking into FatSecret.com's REST API this weekend after wondering how hard it would be to make a Food Tracking App for Win Phone 7.


My first step was to take a look at their REST API Documentation, which shows a list of their available methods and how to authenticate using OAuth. It turns out, the hardest part of being able to get data from their services is the OAuth stuff.


OAuth Primer



Basically, OAuth works like this; You get a Consumer Key and a Consumer Secret from their API. You use these to "Sign" your requests. The "Signature" is built up using 3 components joined by an "&"; The method you are using to request (POST or GET), the uri of the service (Url Encoded), and the query parameters (ordered alphabetically by key, then value, then Url Encoded).



{HTTPMethod}&{ServiceUrl}&{OrderedParameters}

GET&http://platform.fatsecret.com/rest/server.api&a=foo&oauth_consumer_key=demo&oauth_nonce=abc&oauth_signature_method=HMAC-SHA1&oauth_timestamp=12345678&oauth_version=1.0&z=bar


Final Version:

GET&http%3A%2F%2Fplatform.fatsecret.com%2Frest%2Fserver.api&a%3Dbar%26%26oauth_consumer_key%3Ddemo%26oauth_nonce%3Dabc%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D12345678%26oauth_version%3D1.0%26z%3Dbar


Then you make your request and hope everything matches up on their side.


FatSecret C# Service Wrappers



The end result of digging into the API was a library to wrap the nutrition and exercise services. Now there is an easy way to search for nutrition information in C#. Visit the FatSecretSharp project on codeplex for examples and code. I'm hoping to start making a Win Phone 7 Application for Food Tracking in the next couple weeks.



Now Playing: Weird Al - Fat