次の方法で共有


Creating ASP.NET Web APIs on Azure Web Sites

Last month the new Windows Azure features were released, and one of them which I found quite interesting was the “Web Sites”. Currently I have an account at GoDaddy.com where I host my “pet projects”, and now that Windows Azure has a similar feature at a similar pricing plan (for the preview we can have up to 10 sites at no cost, with some limited resource utilization), I felt compelled to “eat my own dogfood” and try it out. And I really loved it – especially the integration with the Git source control, where uploading new bits to the site feels like a breeze. Before I start to look too much like a homer, I liked the Git integration better than the GoDaddy interface because I prefer working with command-line interfaces. If you’re into GUI, you can use something like a FTP client to talk to GoDaddy, or one of the many Git shells (which I haven’t tried), so which one is better would depend on your opinion of the tools.

So I decided to convert one of my old projects which I have hosted in the GoDaddy account to work in an Azure Web Site. The project, a tool which converts a JSON document into a class (or series of classes) and an operation contract that can be used in WCF, was written using some classes from a Codeplex project which isn’t supported anymore (WCF Web APIs). So when migrating to Azure, I decided to also try the integration between that and the replacement for that project, ASP.NET Web APIs. Here’s a step-by-step account of what I did, hopefully you’ll find it useful when trying to do something similar.

So here’s a very detailed step-by-step of what I did – and similarly, what you’d do – to create my API and host it in Azure. I hope you like huge blog posts…

If you’re only interested in the tool to convert JSON to data contracts: see it at https://jsontodatacontract.azurewebsites.net, or you can get the source code in the MSDN Code Gallery.

1. Create the site

The first thing to do is to create the new Web Site which will host the API. I tried the name jsontodatacontract, and it was available:

AzureWebSiteWebApi-1-CreateSite

After creating the web site, there are a few options for deploying the site. We could use the Publish option in any web project (or web site) in Visual Studio, or we can also use the integrated source control system in the web sites. Since I want to have source control, and I like Git’s simplicity, I’ll choose that option.

AzureWebSiteWebApi-2-SetUpGit

Now the repository is created (one click!), and it’s ready to be used. The next page shows the next steps we can take to upload data to the site.

AzureWebSiteWebApi-3-GitDone

2. Create the project

So far we don’t have any data to push to the web site, so let’s take a back off the Azure portal for a while, and let’s create a new project which we’ll use. Since all I want (for now) is to build a service, I’ll create an empty project, so I’ll use an Empty ASP.NET Web Application instead of the full MVC4 application This will make our code as small as possible, but we’ll need to deal with things such as routing ourselves – which are not too hard, as we’ll see shortly. Another advantage of this method (empty project) is that we don’t need to install anything, and to get the ASP.NET MVC 4 templates you need to, well, install the ASP.NET MVC 4 templates (which currently are in the RC version, and many people – myself included – don’t like to install pre-release software in their machines). The RC is at a good quality already, but since in this case I don’t need it, I like to keep things simple whenever possible.

So let’s open the New Project dialog, and create an empty ASP.NET Web Application. I’ll save it in the c:\git directory, but that’s just my preference (the root of my Git repositories), it could be created in any folder in your machine. One setting which we should use is the “Create directory for solution” checkbox, as it will allow us to use the NuGet Package Restore feature, which I’ll cover later on.

AzureWebSiteWebApi-4-CreateProject

Once the project is created, let’s add a reference to the binaries for Web API. Right-clicking the project, select “Manage NuGet Packages…”, and that will bring NuGet’s dialog, where we can search for “webapi”, and select the “Microsoft ASP.NET Web API (RC)” package (while the final version isn’t available). Clicking “Install” will download that package (and all of its dependencies). It will also add them as references to the project, so they can be used right away.

AzureWebSiteWebApi-5-AddNugetPackage

One parenthesis here – I started using NuGet about a year ago, and I really like it. It’s a feature which comes installed by default on Visual Studio 2012. For Visual Studio 2010, you can install it, from a link in the main NuGet page: https://www.nuget.org/. My only gripe is that it doesn’t work on the express editions of VS 2010 (C# or VB) – it does work on Visual Web Developer, though. I think it will work on the express editions for VS 2012 as well, though.

Ok, back to the project. We need to create a controller class which implements our service. In Web APIs, controller classes can be defined anywhere in the project, but to keep with the convention, I’ll add it in a folder called “Controllers”, which I’ll add to the project.

AzureWebSiteWebApi-6-AddControllerFolder

And we can now add our controller class in that folder, by right-clicking on the folder and selecting “Add” –> “New Item”, then selecting a new Web API Controller Class.

AzureWebSiteWebApi-7-AddController

For now, let’s remove the implementation of the class, and add a pair of Post / Get methods to see if the deployment to Azure will work.

  1. public class JsonToDataContractController : ApiController
  2. {
  3.     // POST api/<controller>
  4.     public HttpResponseMessage Post(HttpRequestMessage value)
  5.     {
  6.         var response = Request.CreateResponse(HttpStatusCode.OK);
  7.         response.Content = new StringContent("I'll be running on an Azure Web Site!");
  8.         return response;
  9.     }
  10.  
  11.     public HttpResponseMessage Get()
  12.     {
  13.         var response = this.Request.CreateResponse(HttpStatusCode.OK);
  14.         response.Content = new StringContent("I'll be running on an Azure Web Site!");
  15.         return response;
  16.     }
  17. }

One more thing with the project: since we used an empty template, we need to add the routing code ourselves. So let’s add a new Global Application Class

AzureWebSiteWebApi-8-AddGlobalAsax

And add the routing in the code:

  1. using System;
  2. using System.Web.Http;
  3.  
  4. namespace JsonToDataContract
  5. {
  6.     public class Global : System.Web.HttpApplication
  7.     {
  8.         protected void Application_Start(object sender, EventArgs e)
  9.         {
  10.             GlobalConfiguration.Configuration.Routes.MapHttpRoute(
  11.                 name: "api",
  12.                 routeTemplate: "api/{controller}",
  13.                 defaults: new { controller = "JsonToDataContract" });
  14.         }
  15.     }
  16. }

At this point we can “F5” our application to see if it works. By default the project will browse to the root “/”, but our controller is at the “api/” URL (based on the route template), so we need to browse to that location.

AzureWebSiteWebApi-9-F5

And since also added a Post operation, we should be able to call it as well, which I can do with Fiddler:

AzureWebSiteWebApi-10-PostWithFiddler

3. Add source control using Git

Ok, we now have a Web API which is functional, so we can start the deployment process. The last page in the portal showed what to do, so let’s go to the root of the application (c:\git\JsonToDataContract) and initialize our git repository.

AzureWebSiteWebApi-11-GitInit

Now we can see which files Git wants to track by using the git status command

AzureWebSiteWebApi-12-GitStatus

There are two things which Git wants to track but we don’t really need to deploy. One is the .suo (Solution User Options) file for the solution, which doesn’t need to be shared. The other are the NuGet packages which are stores in the packages/ directory. Git, like other DVCS, don’t work too well with binary files, since updating them can cause the repository to grow a lot over time. Thankfully, NuGet has a feature called NuGet Package Restore, which allows us to bypass checking the packages in, and during the build it will download any missing packages locally. To do that, let’s right-click the solution in the VS Solution Explorer, and choose the “Enable NuGet Package Restore” option.

AzureWebSiteWebApi-13-EnableNugetPackageRestore

What the feature did was to add a new solution folder (.nuget), and on it add a new targets file and a small NuGet executable which can be used, during build, to download missing packages.

AzureWebSiteWebApi-14-NugetPackageRestoreEnabled

Now we can exclude the packages directory (along with the .suo file) from Git, and to do that we’ll need a .gitignore file on the Git root (in my case, c:\git\JsonToDataContract\.gitignore).

AzureWebSiteWebApi-15-GitIgnore

Now we can add the project, and see what is to be committed.

AzureWebSiteWebApi-16-GitAdd

And finally let’s commit our changes:

AzureWebSiteWebApi-17-GitCommit

4. Deploy to Azure

So we committed the changes to our local repository, but nothing has been pushed to Azure yet. Let’s do that, again following the instructions on the Azure Web Site page:

AzureWebSiteWebApi-18-GitPush

A lot happened when we pushed to the web site. First, the we pushed our repository to the server. There, when the transfer finished, the site started the automatic deployment, by building the project. Notice that since we excluded the NuGet packages (but enabled package restore), the packages were downloaded during the build as well. And when the build was done, the site was deployed, so we can go to https://jsontodatacontract.azurewebsites.net/api and see the same page that we saw locally:

AzureWebSiteWebApi-19-DeploymentSucceeded

And our Web API is running on Azure!

5. Checkpoint

Ok, what do we have so far? We created a web site on Azure, set up Git publishing, created a project using Web API via NuGet, and deployed the project to Azure. As far as “how to create and deploy a Web API in Azure”, that’s it. I’ll continue on, though, to finish the project which I set out to do.

6. Update controller – converting JSON to classes

Ok, let’s make the controller do what it’s supposed to do. Since I already had a project which did something similar, I’ll reuse some of the code from that project in this one and let me start saying that this is definitely not the most beautiful code you’ll see, but since it works, I decided not to fiddle too much with it.

The task of converting arbitrary JSON to classes which represent it can be broken down in two steps. First, we need to look at the JSON and see if it has a structure which actually can be represented in classes – there are some which don’t, such as an array containing both objects and primitives. By doing that we can load the JSON into a memory structure, similar to a DOM, with information about the types. Second, we need to traverse that DOM and convert it into the classes which we’ll use to consume and produce that JSON.

For the first part, I’ll define a class called JsonRoot, which can represent either a primitive type (string, boolean, numbers) or a complex type which contains members. The members (or the root itself) can be part of an array, so we also store the rank (or number of dimensions) of the array in the JsonRoot type (“rank” is an overloaded term which if more often used with rectangular array, but in this scenario it actually means jagged arrays which are supported by the serializers). Notice that this class could (maybe should?) be better engineered, split in two so that each type has its own behavior, but I won’t go there, at least not in this iteration.

  1. public class JsonRoot
  2. {
  3.     public bool IsUserDefinedType { get; private set; }
  4.     public Type ElementType { get; private set; }
  5.     public string UserDefinedTypeName { get; private set; }
  6.     public int ArrayRank { get; private set; }
  7.     public IDictionary<string, JsonRoot> Members { get; private set; }
  8.  
  9.     private JsonRoot Parent { get; set; }
  10.  
  11.     private JsonRoot(Type elementType, int arrayRank)
  12.     {
  13.         this.Members = new Dictionary<string, JsonRoot>();
  14.         this.IsUserDefinedType = false;
  15.         this.ElementType = elementType;
  16.         this.ArrayRank = arrayRank;
  17.     }
  18.  
  19.     private JsonRoot(string userDefinedTypeName, int arrayRank, IDictionary<string, JsonRoot> members)
  20.     {
  21.         this.IsUserDefinedType = true;
  22.         this.UserDefinedTypeName = userDefinedTypeName;
  23.         this.ArrayRank = arrayRank;
  24.         this.Members = members;
  25.     }
  26.  
  27.     public static JsonRoot ParseJsonIntoDataContract(JToken root, string rootTypeName)
  28.     {
  29.         if (root == null || root.Type == JTokenType.Null)
  30.         {
  31.             return new JsonRoot(null, 0);
  32.         }
  33.         else
  34.         {
  35.             switch (root.Type)
  36.             {
  37.                 case JTokenType.Boolean:
  38.                     return new JsonRoot(typeof(bool), 0);
  39.                 case JTokenType.String:
  40.                     return new JsonRoot(typeof(string), 0);
  41.                 case JTokenType.Float:
  42.                     return new JsonRoot(typeof(double), 0);
  43.                 case JTokenType.Integer:
  44.                     return new JsonRoot(GetClrIntegerType(root.ToString()), 0);
  45.                 case JTokenType.Object:
  46.                     return ParseJObjectIntoDataContract((JObject)root, rootTypeName);
  47.                 case JTokenType.Array:
  48.                     return ParseJArrayIntoDataContract((JArray)root, rootTypeName);
  49.                 default:
  50.                     throw new ArgumentException("Cannot work with JSON token of type " + root.Type);
  51.             }
  52.         }
  53.     }
  54. }

Parsing primitive types is trivial, as shown above. Parsing objects is also not hard – recursively parse the members and create a user-defined type with those members. When we get to arrays is where the problem starts happening. JSON arrays can contain arbitrary objects, so we need some merging logic so that two similar items can be represented by the same data type.

Here are some examples to illustrate the issue:

  • The array [1, 1234, 1234567, 12345678901] at first can be represented as an array of Int32 values, but the last value is beyond the range of that type, so we must use an array of Int64 instead.
  • Almost all elements in the array [true, false, false, null, true] can be represented by Boolean, except the 4th one. In this case, we can use Nullable<Boolean> instead.
  • This array [1, 2, “hello”, false] could potentially be represented as an array of Object, but that loses too much information, so I decided that it wouldn’t be implemented in this iteration
  • Arrays of objects are more complex. In order to merge two elements of the array, we need to merge the types of all members of the objects, including extra / missing ones. This array – [{“name”:”Scooby Doo”, “breed”:”great dane”}, {“name”:”Shaggy”,”age”:"19}] – would need a type with three members (name, breed, age). And so on.

So here’s how we can parse an array as a JsonRoot. After we try to merge all the elements of the array into one JsonRoot type, we’ll create a new JsonRoot object incrementing the ArrayRank property.

  1. private static JsonRoot ParseJArrayIntoDataContract(JArray root, string rootTypeName)
  2. {
  3.     if (root.Count == 0)
  4.     {
  5.         return new JsonRoot(null, 1);
  6.     }
  7.  
  8.     JsonRoot first = ParseJsonIntoDataContract(root[0], rootTypeName);
  9.     for (int i = 1; i < root.Count; i++)
  10.     {
  11.         JsonRoot next = ParseJsonIntoDataContract(root[i], rootTypeName);
  12.         JsonRoot mergedType;
  13.         if (first.CanMerge(next, out mergedType))
  14.         {
  15.             first = mergedType;
  16.         }
  17.         else
  18.         {
  19.             throw new ArgumentException(string.Format("Cannot merge array elements {0} ({1}) and {2} ({3})",
  20.                 0, root[0], i, root[i]));
  21.         }
  22.     }
  23.  
  24.     if (first.IsUserDefinedType)
  25.     {
  26.         return new JsonRoot(first.UserDefinedTypeName, first.ArrayRank + 1, first.Members);
  27.     }
  28.     else
  29.     {
  30.         return new JsonRoot(first.ElementType, first.ArrayRank + 1);
  31.     }
  32. }

The code for merging two types can be found in the sample in the code gallery (link on the bottom of this post).

Now, we have a data structure which says which types we need to generate. Let’s move on to the code generation part. In this example (and in the original post), I’m using the types in the System.CodeDom namespace, since it gives me for free the generation in different languages. I’ll add a new class, JsonRootCompiler, which has one method which will write all the types corresponding to the given JsonRoot object (and the root of any members of that object as well) to a text writer.

  1. public class JsonRootCompiler
  2. {
  3.     private SerializationModel serializationModel;
  4.     private string language;
  5.  
  6.     /// <summary>
  7.     /// Creates a new instance of the JsonRootCompiler class.
  8.     /// </summary>
  9.     /// <param name="language">The programming language in which the code will be generated.</param>
  10.     /// <param name="serializationModel">The serialization model used in the classes.</param>
  11.     public JsonRootCompiler(string language, SerializationModel serializationModel)
  12.     {
  13.         this.language = language;
  14.         this.serializationModel = serializationModel;
  15.     }
  16.  
  17.     public void GenerateCode(JsonRoot root, TextWriter writer)
  18.     {
  19.         CodeCompileUnit result = new CodeCompileUnit();
  20.         result.Namespaces.Add(new CodeNamespace());
  21.         GenerateType(result.Namespaces[0], root, new List<string>());
  22.         CodeDomProvider provider = CodeDomProvider.CreateProvider(this.language);
  23.         CodeGeneratorOptions options = new CodeGeneratorOptions();
  24.         options.BracingStyle = "C";
  25.         provider.GenerateCodeFromCompileUnit(result, writer, options);
  26.     }
  27.  
  28.     private string GenerateType(CodeNamespace ns, JsonRoot root, List<string> existingTypes)
  29.     {
  30.         if (!root.IsUserDefinedType) return null;
  31.  
  32.         CodeTypeDeclaration rootType = new CodeTypeDeclaration(GetUniqueDataContractName(root.UserDefinedTypeName, existingTypes));
  33.         existingTypes.Add(rootType.Name);
  34.         rootType.Attributes = MemberAttributes.Public;
  35.         rootType.IsPartial = true;
  36.         rootType.IsClass = true;
  37.         ns.Types.Add(rootType);
  38.         rootType.Comments.Add(
  39.             new CodeCommentStatement(
  40.                 string.Format(
  41.                     "Type created for JSON at {0}",
  42.                     string.Join(" --> ", root.GetAncestors()))));
  43.  
  44.         AddAttributeDeclaration(rootType, rootType.Name, root.UserDefinedTypeName);
  45.         AddMembers(ns, rootType, root, existingTypes);
  46.         return rootType.Name;
  47.     }
  48. }

Again, the bulk of the implementation can be found in the MSDN Code Gallery sample.

And we have the two pieces that we need, we can update the code for the controller to use them. The Post method has 4 parameters, 3 of type string (which, by default, are expected to come via the request URI, as query string parameters), and of of type JToken, which can be read by the JSON formatter in the Web API framework (it will be read from to the request body).

  1. public HttpResponseMessage Post(JToken value, string rootTypeName, string language, string serializationModel)
  2. {
  3.     JsonRoot root = JsonRoot.ParseJsonIntoDataContract(value, rootTypeName);
  4.     StringBuilder sb = new StringBuilder();
  5.     using (StringWriter sw = new StringWriter(sb))
  6.     {
  7.         JsonRootCompiler compiler = new JsonRootCompiler(
  8.             language,
  9.             (SerializationModel)Enum.Parse(typeof(SerializationModel), serializationModel, true));
  10.         compiler.GenerateCode(root, sw);
  11.         var response = Request.CreateResponse(HttpStatusCode.OK);
  12.         response.Content = new StringContent(sb.ToString());
  13.         return response;
  14.     }
  15. }

Let’s test it now. Since we only have a POST method, we’ll need to either create a custom client, or use something like Fiddler to do that.

AzureWebSiteWebApi-20-FiddlerGeneratingType

7. Creating a simple front-end

At this point our API is done, and can be deployed to Azure via Git. But to make the web site more usable, let’s create a simple front-end where users don’t need to use a tool such as Fiddler to generate JSON. My design skills are quite rudimentary at best, so I won’t even try to make anything fancy and will just use a few HTML controls instead. Let’s first add a new HTML page. Based on the configuration of my Azure Web Site, the first file it will look for when browsing to it is called Default.htm (you can see that in the bottom of the “Configure” page in the portal), so let’s create one in our project.

AzureWebSiteWebApi-21-AddDefaultHtm

And add some code to it:

  1. <body>
  2.     <h1>JSON to Data Contract (or JSON.NET) types</h1>
  3.     <p>Root class name: <input type="text" id="rootClassName" value="RootClass" size="50" /></p>
  4.     <p>Output programming language:
  5.         <select id="progLanguage">
  6.             <option value="CS" selected="selected">C#</option>
  7.             <option value="VB">VB.NET</option>
  8.         </select>
  9.     </p>
  10.     <p>Serialization Model:
  11.         <select id="serializationModel">
  12.             <option value="DataContractJsonSerializer" selected="selected">DataContract</option>
  13.             <option value="JsonNet">JSON.NET</option>
  14.         </select>
  15.     </p>
  16.     <p><b>JSON document:</b><br />
  17.      <textarea id="jsonDoc" rows="10" cols="60"></textarea></p>
  18.     <p><input type="button" id="btnGenerate" value="Generate code!" /></p>
  19.     <p><b>Classes:</b><br />
  20.      <textarea id="result" rows="10" cols="60"></textarea></p>
  21. </body>

Now we need to hook up the button to call our API. We could do that using the native XmlHttpRequest object, but I found that using jQuery is a lot simpler, and we can use NuGet to add a reference to jQuery to our project, so why not?

AzureWebSiteWebApi-22-AddjQueryNugetPackage

We need to add a reference to the jQuery.js on the default.htm file; the easiest way is to simply drag the file jQuery.1.7.2.js from the scripts folder (where it NuGet package created it), and drop it within the <head> section in the HTML file.

  1. <script type="text/javascript">
  2.     $("#btnGenerate").click(function () {
  3.         var url = "./api/JsonToDataContract?language=" + $("#progLanguage").val() +
  4.             "&rootTypeName=" + $("#rootClassName").val() +
  5.             "&serializationModel=" + $("#serializationModel").val();
  6.         var body = $("#jsonDoc").val();
  7.         $.ajax({
  8.             url: url,
  9.             type: "POST",
  10.             contentType: "application/json",
  11.             data: body,
  12.             dataType: "text",
  13.             success: function (result) {
  14.                 $("#result").val(result);
  15.             },
  16.             error: function (jqXHR) {
  17.                 $("#result").val("Error: " + jqXHR.responseText);
  18.             }
  19.         });
  20.     });
  21. </script>

When we click the button, it will then send a request to the Web API, and we can test it out.

AzureWebSiteWebApi-22-FrontEnd

8. Redeploying to Azure

Ok, we’re getting close to the end, I promise. Let’s go back to our command prompt and see what Git is tracking now, using git status:

AzureWebSiteWebApi-24-GitStatus

We have many items which we need to add to the staging area, so let’s do that. Depending on the configuration of your Git client, you may see some warnings about CR/LF mismatches in the jQuery files, but they can be safely ignored.

AzureWebSiteWebApi-25-GitAdd

Now let’s commit the changes locally.

AzureWebSiteWebApi-26-GitCommit

And we’re now finally ready to push to Azure:

AzureWebSiteWebApi-27-GitPush

The deployment succeeded again! That means that we should be ready to go. Let’s try browsing to https://jsontodatacontract.azurewebsites.net, and try out the service…

AzureWebSiteWebApi-28-WebApiRunningOnAzure

And it’s working! On the cloud!

9. Wrapping up

This was probably the longest post I’ve written, but I don’t think this scenario is complex. I wanted to give a full step-by-step description on how to develop ASP.NET Web APIs and deploy them to Azure Web Sites, and the huge number of images is what made this post longer than most. If you like this format, please let me know and I’ll write some more like this, otherwise I’ll go back to shorter posts since they’re just easier to write :-)

[Code on this post]

Comments

  • Anonymous
    July 17, 2012
    Thank you for writing this article.  I have followed the steps thru the deployment thru git.  When a go to the azure website, I get the following error:Server Error in '/' Application.Method not found: 'System.Web.Http.Services.DefaultServices System.Web.Http.HttpConfiguration.get_Services()'.My guess is that the dlls are not correct, but I am not sure why.  It all works on my local, but from the azure website it doesn't work.  Any ideas how to get the right dlls?
  • Anonymous
    November 21, 2012
    Great article. How do I secure web apis hosted on Azure?
  • Anonymous
    May 27, 2013
    I really cannot even describe how much time you saved me. Thanks so much for this awesome and perfect tool!!!
  • Anonymous
    August 20, 2013
    The comment has been removed
  • Anonymous
    November 01, 2013
    The comment has been removed