One of the challenges I've been facing with using an ASP.Net MVC 2 site to return JSON data to my Win Phone 7 app is authenticating my requests. I don't want other people to be able to sniff on the data between my app and the server and start making malicious requests.
I decided that I wasn't going to use the full OAuth authentication strategy, specifically, I boiled it down to three parameters and an HMACSH1 Hashed Signature. Basically, my signature format is this {HTTPMethod}&{ServiceURL}&{LimitedParmString}. Where my LimitedParmString consists of a standard OAuth "Nonce", a Timestamp (seconds from Jan 1, 1970 UTC), and your consumer key. My client (a Win Phone 7 app in this case) includes this signature as a request header called "oauthlite" when they make their service requests to the MVC Website.
On the server side, my custom AuthorizeAttribute named OAuthLiteAuthenticate takes the HTTPContextBase and constructs a signature based on a request header called "oauthlite". The custom request header is just a url encoded parameter string with the 4 parameters; the "Nonce", Timestamp, Consumer Key and Signature Hash computed by the requestor.
Here is an example "oauthlite" header:
You can get the gist of the authentication process by taking a look at this in-memory implementation of an OAuthLiteAuthenticator:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web;
namespace OAuthLite.Server { /// <summary> /// An in memory OAuthLite Authenticator example. /// </summary> public class OAuthLiteDictionaryAuthenticator : IOAuthLiteAuthenticator { // An "in-memory" key repository. private Dictionary<string, string> _keyRepository = new Dictionary<string, string>() { // We only have one client, but you could add more. { "8b84444fbc09448da68ca14f000695cb", "83fbf4ea05df427bb3390d6d9a0fc626" } };
private static string UrlFormat = "{0}://{1}";
#region IOAuthLiteAuthenticator Members
public bool Authenticate(HttpContextBase context) { if (context == null || context.Request == null) return false;
var requestUrl = context.Request.Url;
var apiUrl = String.Format(UrlFormat, requestUrl.Scheme, requestUrl.Host); var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), context.Request.HttpMethod.ToUpper());
var header = context.Request.Headers["oauthlite"];
// We expect a parmstring including the signature. if (String.IsNullOrEmpty(header) || !header.Contains("&")) return false;
Dictionary<string, string> parms = new Dictionary<string, string>(); foreach (var parm in header.Split('&')) { string[] s = parm.Split('='); if (s.Length < 2) continue;
parms.Add(s[0], s[1]); }
if (!parms.ContainsKey("signature") || !parms.ContainsKey("key")) return false;
var signature = HttpUtility.UrlDecode(parms["signature"]); parms.Remove("signature");
private string GetSecretByKey(string key) { if (!_keyRepository.ContainsKey(key)) return null;
return _keyRepository[key]; }
#endregion } }
I've updated the Win Phone 7 JSON Services code example to use the new OAuthLiteAuthenticate attribute on an action called "Secrets" on the Product Controller. You can download the example below.
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; } }
// 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 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
// 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();
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).
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.