In this interconnected world, you have to deal with APIs all the time. A lot of the time, you’re just querying them. Retrieving data. Sometimes you’re calling a public
API. Most of the time, you’re calling your own, internal API. If you’re calling your own API, it’s likely that you’ll need to post to it at some point, too. Perhaps you need to send images or documents to it. Or maybe it’s good old fashioned form data. Fire it across via AJAX, job done? That makes sense for an image or a document. But what about sending form data? Chances are your form data’s a bit different from the API data. Better handle that in C# then. But how?
HttpClient to the Rescue Again
In my last post, we looked at a way to call a Web API controller from C#. We’ll build upon that in this post. We’ll see how we can post form data across to an API. We’ll be using our old friend, the HttpClient
.
If you want to follow along, you can grab the Visual Studio solution from the last article. If you'd rather just grab the finished code, you can get it from the link below.
View the original article
In this scenario, we’re calling a simple Web API Controller action. It just adds a new product via a Post
request. We’re not interested in the details of the controller action in this post. Instead, we’ll be focusing on how we make that call. To do that, we’ll add a method to our ApiClient
. This will handle posting JSON-encoded data to an API endpoint.
public interface IApiClient
{
Task<HttpResponseMessage>
PostJsonEncodedContent<T>(string requestUri, T content) where T : ApiModel;
}
public class ApiClient : IApiClient
{
private readonly HttpClient httpClient;
private const string BaseUri = "Http://localhost:28601/";
public ApiClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<HttpResponseMessage>
PostJsonEncodedContent<T>(string requestUri, T content) where T : ApiModel
{
httpClient.BaseAddress = new Uri(BaseUri);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add
(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.PostAsJsonAsync(requestUri, content);
return response;
}
}
How Do We Call the API Client?
PostAsJsonAsync
is a handy extension method from the Microsoft.AspNet.WebApi.Client
Nuget package. It lives in the System.Net.Http.Formatting
DLL. We also need to set the request header to accept JSON and we’re good to go. Now let’s take a look at the ProductClient
:
public interface IProductClient
{
Task<CreateProductResponse> CreateProduct(ProductViewModel product);
}
public class ProductClient : ClientBase, IProductClient
{
private const string ProductsUri = "api/products";
public ProductClient(IApiClient apiClient) : base(apiClient)
{
}
public async Task<CreateProductResponse> CreateProduct(ProductViewModel product)
{
var apiModel = new ProductApiModel
{
CreatedOn = DateTime.Now,
Name = product.Name,
Description = product.Description
};
var createProductResponse = await PostEncodedContentWithSimpleResponse
<CreateProductResponse, ProductApiModel>(ProductsUri, apiModel);
return createProductResponse;
}
}
public abstract class ClientBase
{
private readonly IApiClient apiClient;
protected ClientBase(IApiClient apiClient)
{
this.apiClient = apiClient;
}
protected async Task<TResponse>
PostEncodedContentWithSimpleResponse<TResponse, TModel>(string url, TModel model)
where TModel : ApiModel
where TResponse : ApiResponse<int>, new()
{
using (var apiResponse = await apiClient.PostJsonEncodedContent(url, model))
{
var response = await CreateJsonResponse<TResponse>(apiResponse);
response.Data = Json.Decode<int>(response.ResponseResult);
return response;
}
}
private static async Task<TResponse>
CreateJsonResponse<TResponse>(HttpResponseMessage response) where TResponse : ApiResponse, new()
{
var clientResponse = new TResponse
{
StatusIsSuccessful = response.IsSuccessStatusCode,
ResponseCode = response.StatusCode
};
if (response.Content != null)
{
clientResponse.ResponseResult = await response.Content.ReadAsStringAsync();
}
return clientResponse;
}
}
Here we map a ViewModel
to the API model we need to send to the Web API endpoint. I've kept it simple, but in a production system, I’d recommend using a mapper here. We then post the data across via the ApiClient
. Our API post
action will return the id of the product after creation. We grab that and set it as the Data
property of our response. We can then use that id in our MVC ProductController
. This assumes that you use integers for your ids. Maybe you use string
s instead. It’s trivial to change ApiResponse<int>
in PostEncodedContentWithSimpleResponse
as you need to.
Last Step, the MVC Controller
Now that's all set up, we need to call it from an MVC Controller. We're making an asynchronous call, so our MVC Controller action needs to be asynchronous too.
public class ProductController : Controller
{
private readonly IProductClient productClient;
public ProductController()
{
var apiClient = new ApiClient();
productClient = new ProductClient(apiClient);
}
public ProductController(IProductClient productClient)
{
this.productClient = productClient;
}
public ActionResult CreateProduct()
{
var model = new ProductViewModel();
return View(model);
}
[HttpPost]
public async Task<ActionResult> CreateProduct(ProductViewModel model)
{
var response = await productClient.CreateProduct(model);
var productId = response.Data;
return RedirectToAction("GetProduct", new {id = productId});
}
}
All we're doing here is calling the ProductClient
and then redirecting to the GetProduct
action. We're not worrying about whether there were any errors in the API call. I'll be covering that in a future post. Neither are we focusing on displaying a message to the user after the operation completes. If we were, we could use TempData
for that.
Wrapping Up
We covered a fair bit in this article. Let's recap:
- We added a
post
method to the ApiClient
to post JSON to an API endpoint
- We wrapped that call in a
ProductClient
that called out to a specific endpoint to create a product
- We decoded the integer id value returned from the API call and passed it back to the calling code
- We hooked all of this up within an async Controller action in our MVC code
View the original article