In the Microsoft world we have no shortage of client and server-side HTTP stacks to choose from. Rather than try to cover every possible combination, and to prevent this topic from getting too long-winded, I will keep this part succinct.
Our first major decisions revolve around the following options:
Without getting too deep into it, and certainly without suggesting that this is The One True Way, I personally prefer creating REST-based ASP.NET MVC endpoints for the following reasons:
Since weâve decided to go down the path of ASP.NET MVC for this project, Iâm going to walk you through creating services using MVC. If youâve been following along then you already have your Web project setup. If not, please see Part 2: File â> New Project
Many mobile apps communicate with some kind of 3rd party service(s). The decision you need to make is whether the phone should connect directly to this service, or if there are benefits to having the mobile app connect to your middle-man web service, which will subsequently contact the third party service. The decision here definitely depends on your specific needs, but there are very real advantages to creating a middle-man service. A few of the benefits of include:
The following will walk you through a pretty efficient way for creating and consuming services, while enhancing them very easily over time. Of course, to get the full idea of how it works in practice, make sure you pull down the latest code and check it out.
In RealworldStocks.Client.Core I have a Models folder, which include various classes that inherit from NotifyObject, seen below.
Since you want these same classes to be serialized from your service, you will need them in the web project, but want to keep them in sync as you add, remove, or rename properties. To do this, in RealWorldStocks.Web, right-click in your Models folder and select Add â> Existing ItemâŚ
Then navigate to the RealWorldStocks.Client.Core\Models folder and select the entities you plan on serializing and exposing to your client, but donât just press Add! Next to the Add button is a drop-down arrow, open it and make sure you click Add As Link
Notice how the files have little âshortcutâ overlays on their icons.
Below is the complete StocksController for exposing Snapshots and getting the latest News articles. The following code covers a number of concepts from the beginning of this post. I encourage you to check out the full source code to get an understanding of how they work under the covers.
[AllowJsonGet]
[NoCache]
[Compress]
public class StocksController : Controller
{
private readonly IStocksService _stocksService;
private readonly INewsService _newsService;
public StocksController()
{
_stocksService = new YahooStocksService();
_newsService = new FakeNewsService();
}
public ActionResult GetSnapshots(string[] symbols)
{
var model = _stocksService.GetSnapshots(symbols);
return Json(model);
}
public ActionResult GetNews(string[] symbols)
{
var model = _newsService.GetNews(symbols);
return Json(model);
}
[OutputCache(Duration = 60)]
public ActionResult GetSnapshot(string symbol)
{
return GetSnapshots(new[] { symbol });
}
}
If everything goes as planned you should be able to hit this URL in a browser. On my machine hosted under IIS, the URL is:
https://localhost/RealWorldStocks.Web/Stocks/GetSnapshots?symbols=MSFT&symbols=NOK
Now that we have our service up there and returning JSON, letâs start consuming it from the app. The following HttpClient handles a number of concerns on our behalf.
public static class HttpClient
{
public static TimeSpan Timeout = TimeSpan.FromSeconds(30);
public static void BeginRequest<T>(HttpRequest<T> request, Action<HttpResponse<T>> callback)
{
BeginRequest(request.Url, callback);
}
public static void BeginRequest<T>(string url, Action<HttpResponse<T>> callback)
{
var client = new GZipWebClient();
var timer = new Timer(state => client.CancelAsync(), null, Timeout, TimeSpan.FromMilliseconds(-1));
Debug.WriteLine("HTTP Request: {0}", url);
client.DownloadStringCompleted += (s, e) => ProcessResponse(callback, e);
client.DownloadStringAsync(new Uri(url, UriKind.Absolute), timer);
}
private static void ProcessResponse<T>(Action<HttpResponse<T>> callback, DownloadStringCompletedEventArgs e)
{
var timer = (Timer) e.UserState;
if (timer != null)
timer.Dispose();
try
{
if (e.Error == null)
{
string json = e.Result.Replace("&", "&");
Debug.WriteLine("HTTP Response: {0}\r\n", json);
var model = SerializationHelper.Deserialize<T>(json);
Deployment.Current.Dispatcher.BeginInvoke(() => callback(new HttpResponse<T>(model)));
}
else
{
throw new WebException("Error getting the web service data", e.Error);
}
}
catch (SerializationException ex)
{
var httpException = new HttpException("Unable to deserialize the model", ex);
Debug.WriteLine(ex);
Deployment.Current.Dispatcher.BeginInvoke(() => callback(new HttpResponse<T>(httpException)));
}
catch (WebException ex)
{
var httpException = new HttpException(ex);
Debug.WriteLine(ex);
Deployment.Current.Dispatcher.BeginInvoke(() => callback(new HttpResponse<T>(httpException)));
}
}
}
The logging is helpful to see the following in the Debug window as youâre testing the app.
INFO: HTTP Request: https://legacy/RealWorldStocks.Web/Stocks/GetSnapshots?symbols=MSFT&symbols=NOK&symbols=AAPL&isTrial=False&clientVersion=1.0.0.0
INFO: HTTP Response: [{"Symbol":"MSFT","Company":"Microsoft Corpora","OpeningPrice":27.08,"LastPrice":27.19,"DaysChange":0.03,"DaysChangePercentFormatted":"+0.11%","DaysChangeFormatted":"+0.03","DaysRangeMin":0,"DaysRangeMax":0,"Volume":56897792,"PreviousClose":27.16}]
Calling an HTTP endpoint is as simple as declaring the URL along with any query string parameters to customize the request. Since we definitely donât want to spread this implementation detail all through our code-base, we write a simple abstraction which handles the following:
public class StocksWebService : HttpService, IStocksWebService
{
public StocksWebService()
{
#if DEBUG
BaseUrl = DynamicLocalhost.ReplaceLocalhost("https://localhost/RealWorldStocks.Web/");
#else
BaseUrl = "https://services.mydomain.com/v1/";
#endif
}
public HttpRequest<StockSnapshot> GetSnapshot(string symbol)
{
var queryString = new QueryString
{
{"symbol", symbol}
};
return CreateHttpRequest<StockSnapshot>("Stocks/GetSnapshot", queryString);
}
public HttpRequest<IEnumerable<StockSnapshot>> GetWatchListSnapshots()
{
var queryString = new QueryString();
queryString.AddMany("symbols", WatchList.Current.Select(m => m.Symbol));
return CreateHttpRequest<IEnumerable<StockSnapshot>>("Stocks/GetSnapshots", queryString);
}
public HttpRequest<IEnumerable<News>> GetNewsForWatchList()
{
var queryString = new QueryString();
queryString.AddMany("symbols", WatchList.Current.Select(m => m.Symbol));
return CreateHttpRequest<IEnumerable<News>>("Stocks/GetNews", queryString);
}
}
DynamicLocalhost is a NuGet package I wrote to make debugging services on multiple machines with multiple developers easier.
You can read more about the DynamicLocalhost package here.
It seems like a lot of code just to get this far, but most of this stuff is infrastructure that I just copy/paste into all of my projects. Without these infrastructure concerns I am able to just start writing the controller methods, and defining the client-side API for the requests. From there I just start using it from the ViewModel, all in a matter of 5 minutes.
As a quick aside I wanted to talk about coroutines. One of these amazing things was first introduced to me in Rob Eisenbergâs Build your own MVVM Framework talk from MIX â10. It was an excellent talk, and the inspiration for the Caliburn.Micro project.
The Coroutine is the UpdateWatchList method below.
This is actually a lot of stuff going on, and itâs async. Notice how there are no lambdas, no callbacks, no anonymous method delegates? Itâs really quite elegant, and has suited me very nicely while I patiently wait for C# 5 and official async compiler support.
private IEnumerable<IResult> UpdateWatchList()
{
BusyIndictator.Show("Loading watch list...");
var request = _stocksWebService.GetWatchListSnapshots().Execute();
yield return request;
if (!request.Response.HasError)
{
WatchList.RepopulateObservableCollection(request.Response.Model);
}
else
{
MessageBox.Show("We had troubles updating your watch list, please try again in a few moments",
"Unable to contact server", MessageBoxButton.OK);
}
yield return BusyIndictator.HideResult();
}
That covered a lot of stuff. My hope is that it wasnât too daunting or overwhelming â a lot of this code really is reusable infrastructure stuff that can just be pasted into any project and used right-away. I would love feedback from people, particularly those who choose a different path, like WCF and Service References.
Leave a Comment