Web APIs with CORS

Summary

I’ve done a lot of .Net Web APIs.  APIs are the future of web programming.  APIs allow you to break your system into smaller systems to give you flexibility and most importantly scalability.  It can also be used to break an application into front-end and back-end systems giving you the flexibility to write multiple front-ends for one back-end.  Most commonly this is used in a situation where your web application supports browsers and mobile device applications.

Web API

I’m going to create a very simple API to support one GET Method type of controller.  My purpose is to show how to add Cross Origin Resource Sharing CORS support and how to connect all the pieces together.  I’ll be using a straight HTML web page with a JQuery page to perform the AJAX command.  I’ll also use JSON for the protocol.  I will not be covering JSONP in this article.  My final purpose in writing this article is to demonstrate how to troubleshoot problems with APIs and what tools you can use.

I’m using Visual Studio 2015 Community edition.  The free version.  This should all work on version 2012 and beyond, though I’ve had difficulty with 2012 and CORS in the past (specifically with conflicts with Newtonsoft JSON).

You’ll need to create a new Web API application.  Create an empty application and select “Web API” in the check box.  

Then add a new controller and select “Web API 2 Controller – Empty”.

Now you’ll need two NuGet packages and you can copy these two lines and paste them into your “Package Manager Console” window and execute them directly:

Install-Package Newtonsoft.Json
Install-Package Microsoft.AspNet.WebApi.Cors

For my API Controller, I named it “HomeController” which means that the path will be:

myweburl/api/Home/methodname

How do I know that?  It’s in the WebApiConfig.cs file.  Which can be found inside the App_Start directory.  Here’s what is default:

config.Routes.MapHttpRoute(
    name: “DefaultApi“,
    routeTemplate: “api/{controller}/{id}“,
    defaults: new { id = RouteParameter.Optional }
);

The word “api” is in all path names to your Web API applications, but you can change that to any word you want.  If you had two different sets of APIs, you can use two routes with different patterns.  I’m not going to get any deeper here.  I just wanted to mention that the “routeTemplate” will control the url pattern that you will need in order to connect to your API.

If you create an HTML web page and drop it inside the same URL as your API, it’ll work.  However, what I’m going to do is run my HTML file from my desktop and I’m going to make up a URL for my API.  This will require CORS support, otherwise the API will not respond to any requests.

At this point, the CORS support is installed from the above NuGet package.  All we need is to add the following using to the WebApiConfig.cs file:

using System.Web.Http.Cors;

Then add the following code to the top of the “Register” method:

var cors = new EnableCorsAttribute(“*“, “*“, “*“);
config.EnableCors(cors);


I’m demonstrating support for all origins, headers and methods.  However, you should narrow this down after you have completed your APIs and are going to deploy your application to a production system.  This will prevent hackers from accessing your APIs.

Next, is the code for the controller that you created earlier:

using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApiCorsDemo.Models;
using Newtonsoft.Json;
using System.Text;

namespace WebApiCorsDemo.Controllers
{
    public class HomeController : ApiController
    {
        [HttpGet]
        public HttpResponseMessage MyMessage()
        {
            var result = new MessageResults
            {
                Message = “It worked!
            };

            var jsonData = JsonConvert.SerializeObject(result);
            var resp = new HttpResponseMessage(HttpStatusCode.OK);
            resp.Content = new StringContent(jsonData, Encoding.UTF8, “application/json“);
            return resp;
        }
    }
}
 
You can see that I serialized the MessageResults object into a JSON message and returned it in the response content with a type of application/json.  I always use a serializer to create my JSON if possible.  You can generate the same output using a string and just building the JSON manually.  It works and it’s really easy on something this tiny.  However, I would discourage this practice because it becomes a programming nightmare when a program grows in size and complexity.  Once you become familiar with APIs and start to build a full-scale application, you’ll be returning large complex data types and it is so easy to miss a “{” bracket and spend hours trying to fix something that you should not be wasting time on.

The code for the MessageResults class is in the Models folder called MessageResults.cs:

public class MessageResults
{
    public string Message { get; set; }
}

Now we’ll need a JQuery file that will call this API, and then we’ll need to setup IIS.

For the HTML file, I created a Home.html file and populated it with this:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset=”utf-8” />
    <script src=”jquery-2.1.4.min.js“></script>
    <script src=”Home.js“></script>
</head>
<body>
    Loading…
</body>
</html>

You’ll need to download JQuery, I used version 2.1.4 in this example, but I would recommend going to the JQuery website and download the latest version and just change the script url above to reflect the version of JQuery that you’re using.  You can also see that I named my js file “Home.js” to match my “Home.html” file.  Inside my js file is this:

$(document).ready(function () {
    GetMessage();
});

function GetMessage() {
    var url = “http://www.franksmessageapi.com/api/Home/MyMessage“;

    $.ajax({
        crossDomain: true,
        type: “GET“,
        url: url,
        dataType: ‘json‘,
        contentType: ‘application/json‘,
        success: function (data, textStatus, jqXHR) {
            alert(data.Message);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            alert(formatErrorMessage(jqXHR, textStatus));
        }
    });
}

There is an additional “formatErrorMessage()” function that is not shown above, you can copy that from the full code I posted on GitHub, or just remove it from your error return.  I use this function for troubleshooting AJAX calls.  At this point, if you typed in all the code from above, you won’t get any results.  Primarily because you don’t have a URL named “www.franksmessageapi.com” and it doesn’t exist on the internet (unless someone goes out and claims it).  You have to setup your IIS with a dummy URL for testing purposes.

So open the IIS control panel, right-click on “Sites” and “Add Website”:


For test sites, I always name my website the exact same URL that I’m going to bind to it.  That makes it easy to find the correct website.  Especially if I have 50 test sites setup.  You’ll need to point the physical path to the root path of your project, not solution.  This will be the subdirectory that contains the web.config file.

Next, you’ll need to make sure that your web project directory has permissions for IIS to access.  Once you create the website you can click on the website node and on the right side are a bunch of links to do “stuff”.  You’ll see one link named “Edit Permissions”, click on it.  Then click on the “Security” tab of the small window that popped up.  Make sure the following users have full permissions:

IUSR
IIS_IUSRS (yourpcnameIIS_IUSRS)

If both do not exist, then add them and give them full rights.  Close your IIS window.

One more step before your application will work.  You’ll need to redirect the URL name to your localhost so that IIS will listen for HTTP requests.

Open your hosts file located in C:WindowsSystem32driversetchosts.  This is a text file and you can add as many entries into this file that you would like.  At the bottom of the hosts file, I added this line:

127.0.0.1        www.franksmessageapi.com

You can use the same name, or make up your own URL.  Try not to use a URL that exists on the web or you will find that you cannot get to the real address anymore.  The hosts file will override DNS and reroute your request to 127.0.0.1 which is your own PC.

Now, let’s do some incremental testing to make sure each piece of the puzzle is working.  First, let’s make sure the hosts table is working correctly.  Open up a command window.  You might have to run as administrator if you are using Windows 10.  You can type “CMD” in the run box and start the window up.  Then execute the following command:

ping www.franksmessageapi.com

You should get the following:


If you don’t get a response back, then you might need to reboot your PC, or clear your DNS cache.  Start with the DNS cache by typing in this command:

ipconfig /flushdns

Try to ping again.  If it doesn’t work, reboot and then try again.  After that, you’ll need to select a different URL name to get it to work.  Beyond that, it’s time to google.  Don’t go any further until you get this problem fixed.

This is a GET method, so let’s open a browser and go directly to the path that we think our API is located.  Before we do that, Rebuild the API application and make sure it builds without errors.  Then open the js file and copy the URL that we’ll call and paste it into the browser URL.  You should see this:


If you get an error of any type, you can use a tool called Fiddler to analyze what is happening.  Download and install Fiddler.  You might need to change Firefox’s configuration for handling proxies (Firefox will block Fiddler, as if we needed another problem to troubleshoot).  For the version of Firefox as of this writing (42.0), go to the Options, Advanced, Network, then click the “Settings” button to the right of the Connection section.  Select “Use system proxy settings”.

OK, now you should be able to refresh the browser with your test URL in it and see something pop up in your Fiddler screen.  Obviously, if you have a 404 error, you’ll see it long before you notice it on Fiddler (it should report 404 on the web page). This just means your URL is wrong.

If you get a “No HTTP resource was found that matches the request URI” message in your browser, you might have your controller named wrong in the URL.  This is a 404 sent back from the program that it couldn’t route correctly.  This error will also return something like “No type was found that matches the controller named [Home2]” where “Home2” was in the URL, but your controller is named “HomeController” (which means your URL should use “Home”).

Time to test CORS.  In your test browser setup, CORS will not refuse the connection.  That’s because you are requesting your API from the website that the API is hosted on.  However, we want to run this from an HTML page that might be hosted someplace else.  In our test we will run it from the desktop.  So navigate to where you created “Home.html” and double-click on that page.  If CORS is not working you’ll get an error.  You’ll need Fiddler to figure this out.  In Fiddler you’ll see a 405 error.  If you go to the bottom right window (this represents the response), you can switch to “raw” and see a message like this:

HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Allow: GET
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 15 Nov 2015 00:53:34 GMT
Content-Length: 96

<Error><Message>The requested resource does not support http method ‘OPTIONS’.</Message></Error>

The first request from a cross origin request is the OPTIONS request.  This occurs before the GET.  The purpose of the OPTIONS is to determine if the end point will accept a request from your browser.  For the example code, if the CORS section is working inside the WebApiConfig.cs file, then you’ll see two requests in Fiddler, one OPTIONS request followed by a GET request.  Here’s the OPTIONS response:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/10.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: content-type
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 15 Nov 2015 00:58:23 GMT
Content-Length: 0


And the raw GET response:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 24
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
Access-Control-Allow-Origin: *
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 15 Nov 2015 01:10:59 GMT

{“Message”:”It worked!”}

If you switch your response to JSON for the GET response, you should see something like this:


One more thing to notice.  If you open a browser and paste the URL into it and then change the name of MyMessage action, you’ll notice that it still performs a GET operation from the controller, returning the “It worked!” message.  If you create two or more GET methods in the same controller one action will become the default action for all GET operations, no matter which action you specify.  Modify your route inside your WebApiConfig.cs file.  Add an “{action}” to the route like this:

config.Routes.MapHttpRoute(
    name: “DefaultApi“,
    routeTemplate: “api/{controller}/{action}/{id}“,
    defaults: new { id = RouteParameter.Optional }
);


Now you should see an error in your browser if the the action name in your URL does not exist in your controller:


Finally, you can create two or more GET actions and they will be distinguished by the name of the action in the URL.  Add the following action to your controller inside “HomeController.cs”:

[HttpGet]
public HttpResponseMessage MyMessageTest()
{
    string result = “This is the second controller“;

    var jsonData = JsonConvert.SerializeObject(result);
    var resp = new HttpResponseMessage(HttpStatusCode.OK);
    resp.Content = new StringContent(jsonData, Encoding.UTF8, “application/json“);
    return resp;
}

Rebuild, and test from your browser directly.  First use the URL containing “MyMessage”:

Then try MyMessagetest:

Notice how the MyMessageTest action returns a JSON string and the MyMessage returns a JSON message object.



Where to Find the Source Code

You can download the full Visual Studio source code at my GitHub account by clicking here



 

Leave a Reply