January 2018 : Instructor-led Online Course in ASP.NET Core 2.0. Conducted by Bipin Joshi. Read more...
Registration for January 2018 batch of ASP.NET Core 2.0 instructor-led online course has already started. Conducted by Bipin Joshi. Register today ! Click here for more details.

Invoke ASP.NET Web API using TypeScript and XMLHttpRequest

Invoking Web API is a quite common task in modern ASP.NET web applications. Usually, developers resort to jQuery Ajax to call the Web API. If you are using a client side framework such as Angular, you may also use the inbuilt ways to make the Ajax calls. However, what if you aren't using these approaches? This article discusses an alternative.

There are two aspects of this problem. Firstly, you wish to call an ASP.NET Web API without using jQuery or any other framework. The solution to this problem is to use XMLHttpRequest object of HTML5. The second aspect of the problem is how to neatly organize the client side code that calls the Web API. That's where TypeScript is going to help.

Do we need TypeScript for seemingly simple task of calling a Web API? Well, that depends on the complexity of the application. If you are calling a simple Web API and client side processing is minimum you may not use TypeScript at all. However, TypeScript can be a good choice in the following situations:

  • You want to neatly organize your client side code.
  • You are using a same set of Web APIs at many places in your application.
  • Your client side logic that involves Web API calls is quite complex and lengthy.
  • If you are already using TS in your application, you may wish to use stick to OO style for the sake of consistency.

In this article we are going to use TypeScript and XMLHttpRequest to invoke the Web API. Let's begin.

DbContext and Customer class

Create a new ASP.NET Web Application using Empty project template. Make sure to check the MVC and Web API checkboxes while creating the project.

Then add a Entity Framework Code First model for the Customers table of Northwind database. You add them to the Models folder. The Northwind DbContext class and the Customer class is shown below:

public partial class Northwind : DbContext
{
    public Northwind()
        : base("name=Northwind")
    {
    }

    public virtual DbSet<Customer> Customers { get; set; }

    protected override void OnModelCreating
(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .Property(e => e.CustomerID)
            .IsFixedLength();
    }
}

 

public partial class Customer
{
    [StringLength(5)]
    public string CustomerID { get; set; }

    [Required]
    [StringLength(40)]
    public string CompanyName { get; set; }

    [StringLength(30)]
    public string ContactName { get; set; }

    [StringLength(15)]
    public string Country { get; set; }
}

Note that the Customers table contains a few more columns than shown here. For the sake of simplicity we use only CustomerID, CompanyName, ContactName and Country.

Customer Web API controller

Now add a Web API controller and write the following code to it:

public class CustomerController : ApiController
{

    public List<Customer> Get()
    {
        using (Northwind db = new Northwind())
        {
            return db.Customers.ToList();
        }
    }

    public Customer Get(string id)
    {
        using (Northwind db = new Northwind())
        {
            return db.Customers.Find(id);
        }
    }

    public string Post(Customer obj)
    {
        using (Northwind db = new Northwind())
        {
            db.Customers.Add(obj);
            db.SaveChanges();
            return "Customer added successfully!";
        }
    }

    public string Put(string id, Customer obj)
    {
        using (Northwind db = new Northwind())
        {
            db.Entry(obj).State = EntityState.Modified;
            db.SaveChanges();
            return "Customer modified successfully!";
        }
    }

    public string Delete(string id)
    {
        using (Northwind db = new Northwind())
        {
            db.Entry(db.Customers.Find(id)).State 
= EntityState.Deleted;
            db.SaveChanges();
            return "Customer deleted successfully!";
        }
    }
}

We won't discuss this Web API class in detail here. It is suffice to mention that it contains Get(), GetByID(), Post(), Put() and Delete() actions that handle the corresponding HTTP verbs. These actions basically perform the CRUD operations on the database.

Next, add HomeController and Index view. We don't have any specific UI as such. However, for the sake of testing our TypeScript code we need the Index view.

Ok. So far so good. Now it's time to shift our focus to TypeScript. If you are using Visual Studio 2017 you already have TypeScript. If not, follow this link to grab the latest version of TypeScript.

Adding the TypeScript file and skeleton code

Now add Scripts folder to your project root. Then right click on it and add a new TypeScript file named AjaxHelper.ts.

 

The AjaxHelper.ts file will define a module called AjaxHelper. The AjaxHelper module will expose two classes - CustomerApiClient and CustoemrUI. Why do we need two classes? If you look at the Web API calling code, you will realize that there are two distinct concerns - invoking the API and updating the UI based on the API outcome. We try to isolate these tasks in a separate classes for better organization point of view. So, for one "ApiClient" class you can have one or more "UI" classes depending on the pages and the UI.

The following code shows the skeleton of what goes inside the AjaxHelper.ts file (so far).

module AjaxHelper {

    export class CustomerApiClient {

    }

    export class CustomerUI {

    }
}

Some auxiliaries

Before we go ahead and code the CustoemrApiClient class, let's add a couple of things - an enumeration for specifying HTTP verbs and an interface for defining the shape of Customer object that travels between the client and the API. These two are shown below:

enum HttpVerbs {
    GET, POST, PUT, DELETE
}

export interface ICustomer {

    CustomerID: string;
    CompanyName: string;
    ContactName: string;
    Country: string;

}

The HttpVerbs module has four possible values - GET, POST, PUT and DELETE. The ICustomer interface specifies that every customer object should have CustoemrID, CompanyName, ContactName and Country properties of type string.

CustomerApiClient class

Now, add a constructor to the CustomerApiClient class as shown below:

export class CustomerApiClient {

    private baseUrl: string;

    constructor(baseUrl: string) {
        this.baseUrl = baseUrl;
    }
}

Here, we declared a private variable - baseUrl - for storing the base address of the Web API. This base address is assigned in the constructor of the CustomerApiClient class.

Now, let's add a private helper method that will be called by other methods of CustomerApiClient. This helper does the core job of invoking the Web API using XMLHttpRequest object and is discussed next.

private callWebApi(url:string, verb:HttpVerbs, 
data:ICustomer, callback):void {

    let xhr = new XMLHttpRequest();

    xhr.onload = function () {
        let data = JSON.parse(xhr.responseText);
        callback(data);
    }

    xhr.onerror = function () {
        alert("Error while calling Web API");
    }

    let httpVerb: string;
    switch (verb)
    {
        case HttpVerbs.GET:
            httpVerb = "GET";
            break;
        case HttpVerbs.POST:
            httpVerb = "POST";
            break;
        case HttpVerbs.PUT:
            httpVerb = "PUT";
            break;
        case HttpVerbs.DELETE:
            httpVerb = "DELETE";
            break;
    }

    xhr.open(httpVerb, url);

    xhr.setRequestHeader("Content-Type", "application/json");

    if (data == null) {
        xhr.send();
    }
    else {
        xhr.send(JSON.stringify(data));
    }
}

The callWebApi() method accepts four parameters - url, verb, data, and callback. Their purpose is described below:

  • The url parameter indicates the URL of the Web API. This URL is different than the baseUrl private property in that the URL is baseUrl appended with some extra details (CustomerID in this example).
  • The verb parameter is of type HttpVerbs and is used to specify the HTTP verb to be used for making the call to the Web API - GET, POST, PUT, or DELETE.
  • The data parameter is optional. When present it indicates an object that is same in shape as defined by ICustomer interface.
  • The callback parameter is a JavaScript function that is invoked upon successful call to the Web API. This way we isolate the Web API calling login from the UI handling logic.

Inside, the code creates an instance of XMLHttpRequest and wires its two event handlers - onload and onerror. The former event handler will be invoked with the Web API call is successful. The onload handler reads the responseText property and converts the response to JSON format. The responseText could be a single Customer, list of Customer objects, or a string message. The callback function supplied to callWebApi() is then invoked by passing the data object to it.

The onerror handler simply displays an error message to the end user if anything goes wrong in the process.

Then open() method of XMLHttpRequest is called by passing the HTTP verb string and the url. The setRequestHeader() method of XMLHttpRequest sets the request content type to application/json.

Finally, the send() method of XMLHttpRequest is called. Depending on whether data parameter is null or otherwise, send() takes a parameter containing the value to be sent to the Web API. For example, during POST and PUT requests the data parameter will be a valid Customer object that needs to be sent to the Web API. 

Get(callback: any):void {
    this.callWebApi(this.baseUrl, HttpVerbs.GET,
 null, callback);
}

GetByID(id: string, callback: any): void {
    this.callWebApi(`${this.baseUrl}/${id}`, 
HttpVerbs.GET, null, callback);
}


Post(data:ICustomer,callback: any):void {
    this.callWebApi(this.baseUrl, HttpVerbs.POST,
 data, callback);
}


Put(data: ICustomer, callback: any): void {
    this.callWebApi(`${this.baseUrl}/${data.CustomerID}`,
 HttpVerbs.PUT, data, callback);
}


Delete(id: string, callback: any): void {
    this.callWebApi(`${this.baseUrl}/${id}`,
 HttpVerbs.DELETE, null, callback);
}

What follows after the callWebApi() method is a series of public methods - Get(), Post(), Put() and Delete() - that are available to the rest of the client code. These methods internally call the callWebApi() method. Although not shown in this example, these methods can also contain some other pre and post processing depending on the requirements. These methods are quite straightforward and need no explanation. Notice the use of string templates using the back-quotes instead of string concatenation.

CustomerUI class

This completes the CustomerApiClient class. Now let's see CustomerUI class.

GetCallback(data: Array<ICustomer>): void {
    alert(data.length);
}

GetByIDCallback(data:ICustomer):void {
    alert(data.CustomerID);
}

PostCallback(msg:string):void {
    alert(msg);
}

PutCallback(msg: string): void {
    alert(msg);
}

DeleteCallback(msg: string): void {
    alert(msg);
}

The CustomerUI class contains callback functions that are supplied to the CustomerApiClient object. The GetCallback() receives an array of ICustomer objects. Similarly, GetByIDCallback() receives a single ICustomer object. The other three methods receive string messages.

Here, these methods simply call alert() and display some value for testing purpose. In a more realistic here you will have all the UI handling code, say displaying customers as a table or filling some dropdownlist or textboxes. For the sake of simplicity we don't do much of UI handling here.

Invoking the Web API

Ok. Our CustomerApiClient and CustomerUI TypeScript classes are ready. When you save the *.ts files Visual Studio automatically transpiles them to generate *.js files. You may need to click the "Show All  files" option in Solution Explorer to see them.

You need to reference the AjaxHelper.js file from the Index view. So, open the Index view and add this line:

<script src="~/Scripts/AjaxHelper.js"></script>

To test the Web API calls, set breakpoints in all the Web API actions inside the CustomerController class. Then add a script block below the above mentioned script reference and write the following code :

<script>

    var client = new AjaxHelper.CustomerApiClient
("http://localhost:49543/api/customer");
    var ui = new AjaxHelper.CustomerUI();
    client.Get(ui.GetCallback);

</script>

Here, you create an object of AjaxHelper.CustomerApiClient class (remember that AjaxHelper is the module and is analogous to namespaces in C#) by passing the base URL of the Web API. You also create an object of AjaxHelper.CustomerUI class.Finally, you call the Get() method of the CustomerApiClient and pass the GetCallback method as its parameter.

If all goes well, you will reach the breakpoint in the Get() action of the Web API and then you will see an alert box indicating the number of Customer objects returned by the Get() action.

Here is how you can test other methods :

client.GetByID("ALFKI",ui.GetByIDCallback);

var obj = {CustomerID: "ABCDE", CompanyName: 
"Company 2", ContactName: "Contact 2", Country: "USA"};
client.Post(obj,ui.PostCallback);

var obj = {CustomerID: "ABCDE", CompanyName: "Company 2", 
ContactName: "Contact 2", Country: "USA"};
client.Put(obj, ui.PutCallback);

client.Delete("ABCDE",ui.DeleteCallback);

That's it for now ! Keep coding !!


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 16 October 2017


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio