در این مقاله خواهیم دید که چگونه یک Web API را از داخل برنامه دات نت با استفاده
از System.Net.Http.HttpClient فراخوانی کنیم.
در این مثال یک برنامه سمت کلاینت خواهیم نوشت که Web API های نوشته شده زیر را
مورد استفاده قرار می دهد:
Action |
HTTP method |
Relative URI |
Get a product by ID |
GET |
/api/products/id |
Create a new product |
POST |
/api/products |
Update a product |
PUT |
/api/products/id |
Delete a product |
DELETE |
/api/products/id |
برای یادگیری نحوه پیاده سازی API اشاره شده با Asp.Net Web API می توانید به
مقالات پیشین از این سری از مطالب مراجعه فرمایید.
برای ساده سازی مطالب، برنامه کلاینت در این مثال یک برنامه تحت ویندوز Console
است. HttpClient در برنامه های Windows Phone و Windows Store نیز
پشتیبانی می شود.
ایجاد برنامه Console
در Visual Studio یک برنامه Windows Console
ایجاد کرده و آن را HttpClientSample نامگذاری کرده و کد زیر را جایگزین کد
ایجاد شده داخل آن بکنید.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace HttpClientSample
{
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
class Program
{
static HttpClient client = new HttpClient();
static void ShowProduct(Product product)
{
Console.WriteLine($"Name: {product.Name}\tPrice: " + $"{product.Price}\tCategory: {product.Category}");
}
static async Task<Uri>CreateProductAsync(Product product)
{
HttpResponseMessage response = await client.PostAsJsonAsync("api/products", product);
response.EnsureSuccessStatusCode();
// return URI of the created resource.
return response.Headers.Location;
}
static async Task<Product>GetProductAsync(string path)
{
Product product = null;
HttpResponseMessage response = await client.GetAsync(path);
if(response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}
return product;
}
static async Task<Product>UpdateProductAsync(Product product)
{
HttpResponseMessage response = await client.PutAsJsonAsync($"api/products/{product.Id}", product);
response.EnsureSuccessStatusCode();
// Deserialize the updated product from the response body.
product = await response.Content.ReadAsAsync<Product>();
return product;
}
static async Task<HttpStatusCode>DeleteProductAsync(string id)
{
HttpResponseMessage response = await client.DeleteAsync($"api/products/{id}");
return response.StatusCode;
}
static void Main()
{
RunAsync().GetAwaiter().GetResult();
}
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("http://localhost:64195/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Create a new product
Product product = new Product
{
Name = "Gizmo",
Price = 100,
Category = "Widgets"
};
var url = await CreateProductAsync(product);
Console.WriteLine($"Created at {url}");
// Get the product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
// Update the product
Console.WriteLine("Updating price...");
product.Price = 80;
await UpdateProductAsync(product);
// Get the updated product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
// Delete the product
var statusCode = await DeleteProductAsync(product.Id);
Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}
کد نوشته شده بالا یک کد کامل سمت کلاینت است.
RunAsync اجرا شده و تا زمانی که اجرای آن کامل شود بلوکه می شود. بسیاری از
متد های کلاس HttpClient به همین صورت عمل می کنند.به این دلیل که آنها عملیات
I/O درون شبکه انجام می دهند. تمام عملیات آسنکرون داخل RunAsync انجام می
گیرند. به صورت طبیعی، یک برنامه thread اصلی را بلوکه نمی کند، اما این برنامه
اجازه هیج تعاملی را نخواهد داد.
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("http://localhost:64195/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Create a new product
Product product = new Product
{
Name ="Gizmo",
Price = 100,
Category = "Widgets"
};
var url = await CreateProductAync(product);
Console.WriteLine($"Created at {url}");
// Get the product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
// Update the product
Console.WriteLine("Updating price...");
product.Price = 80;
await UpdateProductAsync(product);
// Get the updated product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
// Delete the product
var statusCode = await DeleteProductAsync(product.Id);
Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");
}
catch (Exception e)
{ Console.WriteLine(e.Message);
}
Console.ReadLine();
}
نصب کتابخانه های لازم برای استفاده از امکانات Web API سمت کلاینت
مثل اکثر موارد، در اینجا نیز برای نصب کتابخانه های مورد نیازمان برای استفاده
از امکانات Web API سمت کلاینت ( سرویس گیرنده ) از NuGet Package Manager
استفاده می کنیم.
در Visual Studio از منوی Tools زیر منوی NuGet Package Manager ، روی زیر منوی
Package Manager Console کلیک کنید. در کنسول (PMC) باز شده، عبارت زیر را وارد
کرده و اجرا کنید (کلید اینتر را فشار دهید.)
Install-Package Microsoft.AspNet.WebApi.Client
دستور وارد شده بسته های مورد نیاز زیر را به پروژه ما اضافه خواهد کرد:
- Microsoft.AspNet.WebApi.Client
- Newtonsoft.Json
همانطور که اکثر شما می دانید، Json.Net یک فریموورک Json قدرتمند و با کارایی
بالا برای Dot Net است.
اضافه کردن کلاس مدل
کلاس مدل محصول خود را اینگونه تعریف می کنیم:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
کلاسی که مشاهده می کنید،منطبق با مدل داده ای است که در Web API استفاده شده
است. یک برنامه می تواند از HttpClient برای خواندن اطلاعات یک محصول از طریق
پاسخ HTTP استفاده کند. برنامه نیاز ندارد که کدی برای deserialize کردن
بنویسد.
ایجاد و مقدار دهی اولیه HttpClient
مشخصه استاتیک HttpClient را در نظر می گیریم:
static HttpClient client = new HttpClient();
HttpClient به این صورت در نظر گرفته می شود که یک بار ایجاد و
مقدار دهی گردیده و در طول حیاط پروژه مکررا مورد استفاده قرار می گیرد. شرایط
زیر می توانند منجر به خطای SocketException گردند:
- ایجاد نمونه های جدید برای هر درخواست
- بار سنگین برای سرور
ایجاد نمونه جدید از HttpClient برای هر درخواست ممکن است باعث از بین رفتن
سوکت های موجود شود.
کد زیر یک نمونه از HttpClient را ایجاد، مقدار دهی اولیه و آماده استفاده می
کند
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("http://localhost:64195/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
کد نوشته شده فوق:
-
URI پایه را برای درخواست های HTTP ست می کند. شماره پورت را مطابق با
شماره پورت استفاده شده در برنامه سمت سرور تنظیم می کند. اگر پورت را
منطبق با پورت تنظیم شده سمت سرور قرار ندهیم، برنامه ما کار نخواهد کرد.
-
هدر Accept را برابر با "application/json" قرار می دهد. ست کردن این
هدر به سرور می گوید که دیتای ارسالی را با قالب و پیکربندی JSON ارسال
کند.
ارسال درخواست GET برای واکشی و دریافت اطلاعات
کد زیر یک درخواست GET برای دریافت اطلاعات یک محصول را ارسال می کند
static async Task<Product> GetProductAsync(string path)
{
Product product = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}
return product;
}
متد GetAsync یک درخواست HTTP GET ارسال می کند. زمانی که کار متد تمام شد، یک
شیء از نوع HttpResponseMessage برمی گرداند که شامل پاسخ HTTP است. اگر کد
وضعیت پاسخ کد نشان دهنده موفقیت آمیز بودن کار متد باشد، بدنه پاسخ شامل قالب
JSON از اطلاعات محصول مورد نظر ما خواهد بود. متد ReadAsAsync را
فراخوانی کرده ایم تا قالب JSON را به یک نمونه از شیء محصول تبدیل کنیم. متد
ReadAsAsync به صورت آسنکرون عمل می کند، چون بدنه پاسخ ممکن است شامل حجم
زیادی از اطلاعات باشد.
زمانی که پاسخ HTTP شامل کد خطا باشد، یعنی نشان دهنده وجود خطا باشد،
HttpClient خطا را throw نخواهد کرد. به جای آن مشخصه
IsSuccessStatusCode برابر با مقدار false خواهد بود، اگر کد وضعیت یک کد خطا
باشد. اگر شما ترجیح می دهید با کد های خطای HTTP مانند exception رفتار کنید،
متد HttpResponseMessage.EnsureSuccessStatusCode را در شیء پاسخ
فراخوانی کنید. EnsureStatusCode خطای throwخواهد کرد البته هنگامی که کد خطای
رخ داده بیرون از بازه 200 تا 299 قرار گرفته باشد. در نظر داشته باشید که
HttpClient برای دلایل دیگری نیز می تواند خطا throw کند، مثلا اگر
پاسخگویی به درخواست باعث time out شود.
Media-Type Formatter های مورد نیاز برای deserialize کردن
زمانی که متد ReadAsAsync بدون پارامتر مربوط به Media-Type فراخوانی می
شود، از مجموعه پیش فرض از media formatter ها برای خواندن
بدنه پاسخ استفاده می کند. media formatter های پیش فرض از JSON ، XML و
دیتای Form-url-encoded پشتیبانی می کند.
به جای استفاده از media formatter های پیش فرض ما می توانیم لیستی از
media formatter به متد ReadAsAsync ارسال کنیم. اگر از media-type
formatter های سفارشی و شخصی سازی شده استفاده می کنیم، بهتر است از این
طریق آنها را به متد ReadAsAsync ارسال کنیم. به مثال زیر توجه نمایید:
var formatters = new
List<MediaTypeFormatter>()
{
new MyCustomFormatter(),
new JsonMediaTypeFormatter(),
new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);
ارسال درخواست POST
کد نوشته شده زیر یک درخواست POST ارسال می کند، این درخواست دارای یک
نمونه از شیء product است که در قالب JSON تهیه شده است. یعنی به قالب JSON
تبدیل شده و سپس داخل درخواست ارسال شده است.