Tuesday, July 18, 2017

0 - 60 with Angular 2 & Visual Studio - Part 1

Copyright from: blog.sstorie.com

UPDATE - June 2, 2016
I gave a talk on this topic for the AngularMN meetup in June, and to support that I created a new repository with the the recorded talk, the organized code and links to a couple references. Please check it out. Thanks!
Now back to the original article...
If you spend any time exploring Angular 2 you'll quickly see that most of the content is geared towards developers using text editors like VS Code, Sublime, etc. Those are amazing editors, and I use them both on a regular basis, but I also write a lot of .NET code. That means I use Visual Studio as my primary IDE when writing code every day. It's a truly amazing tool, but it's not always clear how to get it to play nice with some of the web-based frameworks like Angular 2.
With Angular 2 Google made a strategic decision to use Typescript as the language, and I think this will really help Angular 2 adoption. There are tons of devs like me, who are used to awesome compilers, typed languages, and advanced features (generics, async/await, etc) that might have found the quirks of javascript a bit...ahem...frustrating at times. I'm not saying javascript isn't a useful language, or that it's too tough to understand, but I have spent many many hours hunting down simple bugs that a compiler would have caught without missing a beat. By baking Typescript right into Angular 2 the team at Google have made it so much more accessible to developers like myself.
So in this two-part series I want to walk through how a developer who is used to Visual Studio can start using Angular 2 in the IDE they know and love. In this first part we'll build a relatively simple API, and add Nancy to let us host the Angular 2 application using OWIN. In the second post of the series I'll walk through some of my experiences writing Angular 2 code within Visual Studio. For reference, I'm using Visual Studio 2015 Update 2 and ASP.NET 4.5 (i.e., not the bleeding edge ASP.NET Core stuff).
The application
In this example I wrote a simple application that lets a user enter a zip code, and see the past high & low temperatures of the current day over the past 10 years. Visually the Angular 2 app looks like this:
Behind the scenes our API will take the provided zip code, query an external API to map that to a geo-coordinate. That coordinate is then used to query another API for the weather data. Then a response is built up and returned to the Angular app.
Create our Visual Studio web application
To start we can create our visual studio solution using the "blank solution" template:
I usually like to start with a blank solution so that things are named how I want, but you can also start with the ASP.NET web application directly too. Now to add our application we need to add a new project to the solution:
note, if you wanted to just host a website you can use the "add new website" option, but we want to build out an API so we need the web app.
Then, since we're going to be building our API using OWIN we want to start with the Emptytemplate:
Start building out our API
To build out this API we're going to use OWIN, which is a really simple library Microsoft provides to build up a pipeline of actions to take when an HTTP request comes in. The thing I really like about OWIN applications is they are very modular, and it's easy for me to reason about what's actually happening in my application. They can also be hosted in just about anything, including windows services, so they're quite portable. Now, this means we need to plumb things up ourselves, but it's really not too difficult.
To get started we're going to need to pull in the OWIN nuget packages for WebAPI, and another package that will actually kick-start the OWIN process, so in the package manager console go ahead and run the following:
PM> install-package Microsoft.AspNet.WebApi.Owin  
PM> install-package Microsoft.Owin.Host.SystemWeb  
That will pull in a few libraries and give us what we need to host an API using the OWIN pipeline. I also usually look for any updates that are available at this time too since, depending on your settings, Visual Studio will pull in the oldest version of a package available to satisfy any dependencies.
With the nuget packages in place we can start writing some code. With OWIN you need to provide what's called the Startup class. You can either use a convention and call the class Startup, or use an assembly attribute to use something else, but it should look like the following:
using Owin;

namespace WeatherHistory.Web  
{
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {

        }
    }
}
Now this doesn't do anything yet, but if you were to debug this and set a breakpoint on the start of the Configuration method, you would see that it's called:
If you're debugging the project at this point and not hitting the breakpoint, stop and hit the interwebs to figure things out.
Now that we know our OWIN configuration code is being hit we can make some simple updates to the code to add in web api, and even host the API at a specific path within the site. First however, let's add in the shell of our only API controller.
Adding the first WebAPI controller
Within WebAPI there is a base class provided that makes it really simple to add endpoints. You simply derive from a class called ApiController and start adding your functionality. We're going to be building a weather history application, so we'll add a simple endpoint at /api/temperatures that will accept a zip code as a query parameter.
First, we need a model (or class) that we'll be returning from the API. This object will contain the location details, and then includes a list of historical dates:
using System;

namespace WeatherHistory.Web.Models  
{
    public class ZipcodeWeather
    {
        public string City  { get; set; }

        public string State { get; set; }

        public float Latitude { get; set; }

        public float Longitude { get; set; }

        public List<HistoricalTemperature> HistoricalTemperatures { get; set; }

        public ZipcodeWeather()
        {
            HistoricalTemperatures = new List<HistoricalTemperature>();
        }
    }

    public class HistoricalTemperature
    {
        public DateTime Date { get; set; }

        public float Low { get; set; }

        public float High { get; set; }
    }
}
Once we have that we can build out an initial version of our API controller that will accept the zip code as a query parameter, and return a list of historical temps:
using System;  
using System.Collections.Generic;  
using System.Web.Http;  
using WeatherHistory.Web.Models;

namespace WeatherHistory.Web.Api  
{
    // We derive our API contoller from the base class provided by WebAPI, but
    //  we also specify the route prefix at the controller level
    //
    [RoutePrefix("temperatures")]
    public class TemperaturesController : ApiController
    {
        // Now we can use the WebAPI conventions to automatically mark this as 
        //  a GET endpoint, and indicate that it does not add anything to the
        //  route
        //
        [Route("")]
        public IHttpActionResult Get(string zipCode)
        {
            // Create our dummy response just to show the API is working
            //
            var zipcodeWeather = new ZipcodeWeather
            {
                City = "St. Paul",
                State = "MN",
                Latitude = 44.9397629f,
                Longitude = -93.1410727f
            };

            // Now just add a list of fake temperatures to the return object
            //
            zipcodeWeather.HistoricalTemperatures.AddRange(
                new List<HistoricalTemperature> {
                    new HistoricalTemperature { Date = DateTime.Now, High = 75, Low = 50 },
                    new HistoricalTemperature { Date = DateTime.Now.AddYears(-1), High = 75, Low = 50 },
                    new HistoricalTemperature { Date = DateTime.Now.AddYears(-2), High = 75, Low = 50 },
                    new HistoricalTemperature { Date = DateTime.Now.AddYears(-3), High = 75, Low = 50 },
                    new HistoricalTemperature { Date = DateTime.Now.AddYears(-4), High = 75, Low = 50 }
                }
            );

            // Use the WebAPI base method to return a 200-response with the object as
            //  the payload
            //
            return Ok(zipcodeWeather);
        }
    }
}
Now that we have the controller we need to actually wire up WebAPI into the OWIN pipeline. What I normally do is have all my API routes exist underneath a /api/ path within my site. However, I don't want to embed that /api/ in all of my controllers since I might change my mind some day. With OWIN this is super simple, and involves using a simple technique when configuring web api:
using Owin;  
using System.Web.Http;

namespace WeatherHistory.Web  
{
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            // Host all the WebAPI components underneath a path so we can
            //  easily deploy a traditional site at the root of the web
            //  application
            //
            appBuilder.Map("/api", api =>
            {
                // This object is what we use to configure the behavior
                //  of WebAPI in our application
                //
                var httpConfiguration = new HttpConfiguration();

                // We'll use attribute based routing instead of the 
                //  convention-based approach
                //
                httpConfiguration.MapHttpAttributeRoutes();

                // Now add in web api to the OWIN pipeline
                //
                api.UseWebApi(httpConfiguration);
            });
        }
    }
}
With this code in place you can debug the solution, which should automatically open a new page in your default browser. You'll have to update the URL to use the route we specified, but you should see something like this:
Now don't freak out because you're seeing a blob of XML! That's simply because WebAPI supports returning data to the client in either XML or JSON, but if no specific format is requested it will default to XML. If we switch over and use Postman and request JSON we'll see data that looks more familiar perhaps:
Now we're starting to get somewhere, but you're probably noticing that the casing on the properties matches the .NET class. In javascript that isn't the convention used widely, so we need to update WebAPI to serialize our data differently. This involves a simple update the configuration we provided it to it uses camel case when serializing the .NET classes:
                // We'll use attribute based routing instead of the 
                //  convention-based approach
                //
                httpConfiguration.MapHttpAttributeRoutes();

                // Change the serialization so it does camelCase
                //
                var jsonFormatter = httpConfiguration.Formatters.JsonFormatter;
                var settings = jsonFormatter.SerializerSettings;
                settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

                // Now add in web api to the OWIN pipeline
                //
                api.UseWebApi(httpConfiguration);
By updating the JSON formatter our API is using we now get data in the format we want:
Adding real functionality to the API
Ok, now it's time to add some real functionality to our API that will take the zip code provided, map it to a latitude/longitude using zipcodeapi.com, and query The Dark Sky Forecast API to get historical temperature data. In this case the Forecast.IO API provides the historical data we need, but it requires the latitude and longitude to do so. We want to expose a service that only requires the zip code though, so we'll use the zipcodeapi.com API to do that conversion first.
Let's first update our API to convert the zip code, and then we'll worry about pulling the actual data. To query the zip code API we'll use the RestSharp nuget package, which makes it really easy to create REST queries. The docs for that API show that it wants a request like so:
https://www.zipcodeapi.com/rest//info.//  
So let's make a small update to our API controller to make this call:
    // We derive our API contoller from the base class provided by WebAPI, but
    //  we also specify the route prefix at the controller level
    //
    [RoutePrefix("temperatures")]
    public class TemperaturesController : ApiController
    {
        // Now we can use the WebAPI conventions to automatically mark this as 
        //  a GET endpoint, and indicate that it does not add anything to the
        //  route
        //
        [Route("")]
        public IHttpActionResult Get(string zipCode, int? years = 10)
        {
            // Now we can make our first API call to map the zip code
            //  to the geo coordinates we need
            //
            var zipCodeResponse = RequestGeoFromZipcode(zipCode);

            // Let's make sure the zip code was mapped properly before proceeding
            //
            if (zipCodeResponse == null)
            {
                return BadRequest("The zip code could not be mapped to a latitude and longitude");
            }

            var zipcodeWeather = new ZipcodeWeather
            {
                City = zipCodeResponse.City,
                State = zipCodeResponse.State,
                Latitude = zipCodeResponse.Latitude,
                Longitude = zipCodeResponse.Longitude
            };

            // Use the WebAPI base method to return a 200-response with the object as
            //  the payload
            //
            return Ok(zipcodeWeather);
        }

        /// 
        /// Map a zip code string into a response that contains the latitude, 
        /// longitude & city name.
        /// 
/// /// A valid object if the zip code could be mapped, otherwise null in any error condition. private ZipCodeApiResponse RequestGeoFromZipcode(string zipcode) { // Create a RestSharp client we can use to make the API call // var client = new RestClient("https://www.zipcodeapi.com"); // Now build up a request that matches what this API is expecting. We include // out authentication token and the zipcode right in the URI, and that's simple // to do with RestSharp's url segments // var request = new RestRequest("/rest/{apiKey}/info.json/{zipcode}/degrees", Method.GET); request.AddUrlSegment("apiKey", ConfigurationManager.AppSettings["zip-code-api-key"]); request.AddUrlSegment("zipcode", zipcode); // Now any HTTP call will be asynchronous, but this is trivial to handle with // the async/await functionality available in .NET // var response = client.Execute(request); // Let's make sure the request is valid before attempting to decode anything // if (response.StatusCode != HttpStatusCode.OK) { return null; } // Finally, we need to "decode" the JSON data returned so we can load it // into our internal object. // var content = JObject.Parse(response.Content); // Just populate a new object using the key's that existed in the JSON // data returned // var zipCodeResponse = new ZipCodeApiResponse { Zipcode = Convert.ToString(content["zip_code"]), Latitude = Convert.ToSingle(content["lat"]), Longitude = Convert.ToSingle(content["lng"]), City = Convert.ToString(content["city"]), State = Convert.ToString(content["state"]) }; return zipCodeResponse; } }
Here's our updated Web.config that pulls in our API key from a "secret" file:
<configuration>  
  <appSettings file="appSettings.secret">
  </appSettings>
  
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5.2" />
  

  
There are a couple changes to note in this updated code:
  • We check the response from the zip code API to ensure it's valid
  • We're using the JObject class of JSON.net to easily parse the json returned by the API
  • We're adding our API key to the app settings, but using a separate file that isn't checked in
Outside of those two this is mostly straightforward C# code. Let's move on to querying the historical data now. Unlike the zip code API, there is a nice nuget package for querying the Forcast.IO API.
For this application we want to let the user get the past X years of temperatures on the current day for the provided zip code, but default to 10 if the number of years isn't provided. So what we're going to do is update our Get method to take an optional parameters, and add a simple loop to the method to pull out the historical information. Here's the updated method:
      public IHttpActionResult Get(string zipCode, int? years = 10)
        {
            // Now we can make our first API call to map the zip code
            //  to the geo coordinates we need
            //
            var zipCodeResponse = RequestGeoFromZipcode(zipCode);

            // Let's make sure the zip code was mapped properly before proceeding
            //
            if (zipCodeResponse == null)
            {
                return BadRequest("The zip code could not be mapped to a latitude and longitude");
            }

            var zipcodeWeather = new ZipcodeWeather
            {
                City = zipCodeResponse.City,
                State = zipCodeResponse.State,
                Latitude = zipCodeResponse.Latitude,
                Longitude = zipCodeResponse.Longitude
            };

            // Grab the current date so we can create offsets from it
            //
            var startDate = DateTime.Now;

            // Now loop according to 'years' and use the index each time to make a request back
            //  in time
            //
            foreach (var offset in Enumerable.Range(0, (int) years))
            {
                // Calculate the date for this iteration
                //
                var pastDate = startDate.AddYears(-offset);

                // Make the actual forecast.io call
                //
                var request = new ForecastIORequest(ConfigurationManager.AppSettings["forecast-io-key"], zipCodeResponse.Latitude, zipCodeResponse.Longitude, pastDate, Unit.us);
                var response = request.Get();

                // Create the temp object we need to return and add it to the list
                //
                zipcodeWeather.HistoricalTemperatures.Add(new HistoricalTemperature
                {
                    Date = pastDate,
                    High = response.daily.data[0].temperatureMax,
                    Low = response.daily.data[0].temperatureMin
                });
            }

            // Use the WebAPI base method to return a 200-response with the list as
            //  the payload
            //
            return Ok(zipcodeWeather);
        }
Now we've got a nice endpoint that will convert a zipcode into a list of historical weather data. We allow the user to specify how many years of history they want, but don't force them to do so. And all it took was a controller that has just over 130 lines of code (including the comments...which are verbose)!
Add Nancy for hosting web pages
As we build our Angular code base we'll of course need something to host the actual HTML pages, and for that we'll use Nancy. It is a simple library that provides really concise ways of routing HTTP requests to code. Because we're using OWIN, we can't use ASP.NET MVC even if we wanted to. To add what we need just install the nuget package built for hosting Nancy within OWIN (which pulls in the Nancy package automatically):
PM> install-package nancy.owin  
Once we have the package installed we just need to wire Nancy into the OWIN pipeline, create a module that defines our route, and add a simple view that will be rendered for us. To keep things simple I'm just using Nancy's conventions, but you can easily override where files need to be for Nancy to find them. Here's what our project looks like now:
The root module is about as simple as it can get:
using Nancy;

namespace WeatherHistory.Web  
{
    public class RootModule : NancyModule
    {
        public RootModule()
        {
            Get["/"] = _ => View["index"];
        }
    }
}
...and the view is nothing special yet, but it's prepped for our Angular 2 application:
  
<html>

<head>  
</head>

<body>  
    <my-weather-app>
        Nancy is working, but this means there's no Angular 2 yet!
    </my-weather-app>
</body>

</html>  
...and how do we actually wire up Nancy? It's one line of code added right after the appBuilder.Mapblock we created above:
            // Add nancy to the pipeline after WebAPI so the API can
            //  handle any requests it is configured for first
            //
            appBuilder.UseNancy();
So at this point we have a working WebAPI that can provide the weather data, and now a simple website that can provide dynamic HTML content in the same Visual Studio project. This means we can easily run this code, debug into it, and leverage all the power of that Visual Studio provides.
That will conclude part 1 of this series. In the next part we'll tackle adding the Angular 2 code and explore what the experience is like when using Visual Studio.
All of the code for the example application is available in my github repository.

No comments: