Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

Sunday, August 24, 2014

ASP.NET Web Form Model Binding + Web API

Nowadays, many people are talking about Web API and some developers came to me and ask for sample code about how to call an ASP.NET Web API by using the HttpClient class library. Today, I want to share how to send HTTP GET, POST, PUT and DELETE to the ASP.NET Web API from a web page that perform simple CRUD operation.

I have created a very simple address book web application that can display and manipulate the data from the Web API. To begin with the ASP.NET Web API development, create a new empty web project from Visual Studio.



Select the Empty template, as I want to create the latest Web API (version 2.2) by getting the latest assembly from NuGet later.


From the newly created project, open NuGet Package Manager, then search for Microsoft ASP.NET Web API 2.2, then install it.


As I am using the Map HTTP Attribute Routes method to define my routes, therefore, create the class WebApiConfig as follow:

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API routes
        config.MapHttpAttributeRoutes();
    }

}

In the same project, add new Global.asax file if not exist. Then, at the Application_Start event, add this line of code:

GlobalConfiguration.Configure(WebApiConfig.Register);

Service

Now, proceed to create a new controller that support Get, List, Create, Update and Delete function by using HttpGet, HttpPost, HttpPut and HttpDelete attributes like following code:

[RoutePrefix("api/AddressBook")]
public class AddressBookController : ApiController
{
    // GET api/<controller>
    [Route]
    [HttpGet]
    public List<AddressBook> Get()
    {
        AddressBookComponent bc = new AddressBookComponent();
        return bc.List();
    }

    // GET api/<controller>/5
    [Route("{id:int}")]
    [HttpGet]
    public AddressBook Get(int id)
    {
        AddressBookComponent bc = new AddressBookComponent();
        return bc.Get(id);
    }

    // POST api/<controller>
    [Route]
    [HttpPost]
    public void Create(AddressBook addressBook)
    {
        AddressBookComponent bc = new AddressBookComponent();
        bc.Create(addressBook);
    }

    // PUT api/<controller>/5
    [Route("{id:int}")]
    [HttpPut]
    public void Put(int id, AddressBook addressBook)
    {
        AddressBookComponent bc = new AddressBookComponent();
        bc.Update(addressBook);
    }

    // DELETE api/<controller>/5
    [Route("{id:int}")]
    [HttpDelete]
    public void Delete(int id)
    {
        AddressBookComponent bc = new AddressBookComponent();
        bc.Delete(id);
    }

}

[RoutePrefix("api/AddressBook")] attribute is used to create a template URL for your API. As the example above, the Web API URL is base at http://localhost:<port>/api/AddressBook

[Routeattribute is used to make an URL that base on the URL defined in RoutePrefix attribute which link to the particular method directly. For this case, I never specify any named parameter to the Route attribute, hence the URL to call this web function is the same, which is http://localhost:<port>/api/AddressBook. The incoming request will route to the correct function base on the supplied HTTP Verb.

[Route("{id:int}")] has specified named parameter. This attribute will route the request URL that contain parameter with integer type to that particular function. In this case, the URL that will hit that function is http://localhost:<port>/api/AddressBook/5

[Route("{id:int}")]
[HttpPut]
public void Put(int id, AddressBook addressBook)

Note that the above HTTP PUT method contain 2 parameters, one is the ID and another is the object. This is the standard PUT method declaration. If you have the method without the ID parameter, you will get HTTP 405 Method Not Allowed error because it will treat it as POST even you had specified HttpPut attribute.

Client

Now, start to create the client that will consume the above Web API. The Web API can only support JSON and XML HTTP content type, and I pick JSON for light weight payload and it is the commonly used format nowadays. We just need to make sure the HTTP request that sent to the Web API contain the content-type : application/json in the HTTP header. And to do that, I use HttpClient class which comes from the Microsoft ASP.NET Web API 2.2 Client Libraries. You can obtain it from NuGet:



In today's topic, I will also cover about how to use model binding in grid view (ASP.NET Web Form not MVC).

NOTE: Before you begin, I expect that you already had an entity or object which defined as a model.

Create a new ASP.NET web form project, then create a new page. Put a grid view in the page, then define the grid view like this:

<asp:GridView ID="addressBookGrid" runat="server"
    ItemType="AddressBookSample.Entities.AddressBook"
    SelectMethod="addressBookGrid_GetData"
    UpdateMethod="addressBookGrid_UpdateItem"
    DeleteMethod="addressBookGrid_DeleteItem"
    DataKeyNames="ID">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
    </Columns>
</asp:GridView>

First, put the model as the ItemType. Then, define the SelectMethod, UpdateMethod and DeleteMethod, when you do it, Visual Studio will automatically suggest, create and map the events for you. Put the model property name in the DataKeyNames which you think it is unique and can be an identifier for your object.

HTTP GET

The following is the implementation for the SelectMethod. It will send HTTP GET request to the Web API, then bind the responded data into the grid view automatically.

public IQueryable<AddressBook> addressBookGrid_GetData()
{
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:5593");
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.GetAsync("api/AddressBook").Result;
    response.EnsureSuccessStatusCode();

    List<AddressBook> addressBooks = response.Content.ReadAsAsync<List<AddressBook>>().Result;

    return addressBooks.AsQueryable();

}

HTTP POST

The following is the implementation for a Create button. I have a form with text boxes and the Create button to let user enter the information that will be sent to the Web API.

protected void btnCreate_Click(object sender, EventArgs e)
{
    AddressBook addressBook = new AddressBook()
    {
        ID = Convert.ToInt32(createID.Text),
        FirstName = createFirstName.Text,
        LastName = createLastName.Text,
        Contact = createContact.Text
    };

    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:5593");
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.PostAsync<AddressBook>("api/AddressBook", addressBook, new JsonMediaTypeFormatter()).Result;
    response.EnsureSuccessStatusCode();
}

HTTP PUT

The following is the implementation for the UpdateMethod. The method will automatically have the id parameter value which you had selected from the grid view, the code will send HTTP GET request to the Web API to retrieve the specific object first, then update the object, finally send the object with HTTP PUT request to the Web API.

public void addressBookGrid_UpdateItem(int id)
{
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:5593");
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.GetAsync("api/AddressBook/" + id).Result;
    response.EnsureSuccessStatusCode();

    AddressBook item = response.Content.ReadAsAsync<AddressBook>().Result;

    // Load the item here, e.g. item = MyDataLayer.Find(id);
    if (item == null)
    {
        // The item wasn't found
        ModelState.AddModelError("", String.Format("Item with id {0} was not found", id));
        return;
    }
    TryUpdateModel(item);
    if (ModelState.IsValid)
    {
        // Save changes here, e.g. MyDataLayer.SaveChanges();
        HttpResponseMessage updateResponse = client.PutAsync<AddressBook>("api/AddressBook/" + id, item, new JsonMediaTypeFormatter()).Result;
        response.EnsureSuccessStatusCode();
    }

}

HTTP DELETE

The following is the DeleteMethod implementation. The method will also automatically have the ID parameter value which you had selected from the grid view, just simply send the ID with HTTP DELETE request to the Web API as follow:

public void addressBookGrid_DeleteItem(int id)
{
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:5593");
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.DeleteAsync("api/AddressBook/" + id).Result;
    response.EnsureSuccessStatusCode();

}


That's it for a simple CRUD web application that deal with Web API. If you are interested with my source code, feel free to download it from HERE.




Friday, May 17, 2013

WCF REST Service + HttpClient vs WCF Proxy Class - Part 2

Continue from the previous post, I would like to share how to make HTTP GET call to a WCF REST Service.

Following is my operation contract setup:


[OperationContract]
[WebGet(UriTemplate = "ListResults", ResponseFormat = WebMessageFormat.Json)]
List<Result> ListResults();


WCF Service Client

Normally, when we want to call that web service, we can simply perform the "Add Service Reference" from the Visual Studio, and the WCF client aka the proxy class will be auto-generated. Therefore, the following is the code that you normally would use to call a WCF service:


ResultServiceClient proxy = new ResultServiceClient();
List<Result> results = proxy.ListResults();


Well, this code cannot work with the WCF REST service where the operation contract is decorated with WebGet attribute. The reason is WebGet attribute actually make the data retrieval operation expect a GET method call. But, the above code is actually making a POST method call. See the following RAW data which is made by the proxy class:


POST http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Content-Type: application/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo8z4w4PqwblMjjlSjLko010AAAAA6fc873e/5U+GAxRCKLz7Mps+z0ILaFhMs1wQZ9g/XOwACQAA
E2EActivity: DUiXbbtRBk6NgLuSr8X7+A==
Host: 127.0.0.1:65000
Content-Length: 42
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

<ListResults xmlns="http://tempuri.org/"/>

You would get the response HTTP 405 - Method Not Allowed.


HTTP/1.1 405 Method Not Allowed
Server: ASP.NET Development Server/11.0.0.0
Date: Fri, 17 May 2013 00:40:32 GMT
X-AspNet-Version: 4.0.30319
Allow: GET
Content-Length: 1565
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Connection: Close


Therefore, we have to use WebChannelFactory to make the WCF REST service call with HTTP GET method. Here is the code:


var behavior = new WebHttpBehavior();
behavior.DefaultBodyStyle = WebMessageBodyStyle.Wrapped;

//Note: the IResultService is not the one generated from the svcutil.exe
//It should be your Service Contract
using (var factory = new WebChannelFactory<LayeredWebApi.Services.Contracts.IResultService>(
    new WebHttpBinding(),
    new Uri("http://ipv4.fiddler:65000/ResultService.svc")
))
{
    factory.Endpoint.EndpointBehaviors.Add(behavior);

    var channel = factory.CreateChannel();

    //Note: the Result object is not the one generated from the svcutil.exe
    List<LayeredWebApi.Entities.Result> results = channel.ListResults();
}


And, here is the HTTP RAW content made by the WebChannelFactory:


GET http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Content-Type: application/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo6WsSaH5x2NJta5xXQVoGTcAAAAA5M7pLj2DXEOHIOoTRG4R1t5jyJvtJCVLlnuRlW/5RqcACQAA
E2EActivity: BOiPxzfbnUCLg4OkbNlTDw==
Host: 127.0.0.1:65000
Accept-Encoding: gzip, deflate
Connection: Keep-Alive


HttpClient 


There is another way to call the WCF REST service which is by using HttpClient. Here is the code:


using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("http://ipv4.fiddler:65000");
    client.DefaultRequestHeaders.Accept.Add(
        new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

    HttpResponseMessage response = client.GetAsync("ResultService.svc/ListResults").Result;

    Assert.IsTrue(response.IsSuccessStatusCode, "Failed to call WCF service.");
    string json = response.Content.ReadAsStringAsync().Result;

    JObject jObj = JsonConvert.DeserializeObject(json) as JObject;
               
    //Here is the tricky part.
    //WCF REST service return the real result object inside an object property
    //The object property name is prefixed with service: e.g. <ServiceName>Result
    //You know that your result is a generic list, you have to convert the result to JArray first
    //Then convert the JArray to generic List
    List<Result> result = jObj.GetValue("ListResultsResult")
                              .ToObject<JArray>()
                              .ToObject<List<Result>>();

}




See the following RAW content made by the HttpClient:


GET http://127.0.0.1:65000/ResultService.svc/ListResults HTTP/1.1
Accept: application/json
Host: 127.0.0.1:65000
Connection: Keep-Alive



Simple and let's look at the result. Note: There is a tricky part when deal with WCF REST service return result. Look at the following RAW content return from the service:


HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Fri, 17 May 2013 03:35:08 GMT
X-AspNet-Version: 4.0.30319
Content-Length: 286
Cache-Control: private
Content-Type: application/json; charset=utf-8
Connection: Close

{"ListResultsResult":[{"<ID>k__BackingField":1,"<Name>k__BackingField":"Ah Beng","<Score>k__BackingField":50},{"<ID>k__BackingField":2,"<Name>k__BackingField":"Ah Lian","<Score>k__BackingField":72},{"<ID>k__BackingField":3,"<Name>k__BackingField":"Ah Boon","<Score>k__BackingField":1}]}


The List<Result> type has been serialized into JSON as you can see above, however, it is assigned to one property call ListResultsResult as highlighted above. Therefore, in my code with HttpClient, after deserialize the whole JSON string into a JObject (from Newtonsoft), I have to have this code jObj.GetValue("ListResultsResult") to get the real result return by the service method. And then, since the data type is a generic list, it must be a JArray type. I have to convert the result into JArray first, then only convert it into List<Result> type.

Compare to WCF client that use WebChannelFactory, the serialization and deserialization is done at the back with the .net serialization library. It is transparent to you with this one line of code: List<LayeredWebApi.Entities.Result> results = channel.ListResults();

Well, it is up to your call which WCF REST client you wish to use.


Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...