CHAPTER 7

image

Sample Application: Building the Wrapper and Web Client

So far, you have created your application’s domain layer and the API application with ASP.NET Web API framework. Because you now have open endpoints, you can build clients around the HTTP API to consume and modify data.

In terms of HTTP APIs, there are lots of client options. You can build a Windows Phone client, a Windows Store App client, or a web client that directly consumes the HTTP API through Ajax requests. In this chapter, you’ll create a .NET wrapper around the HTTP API, which will make it easy to consume with the HttpClient class. Afterward, you’ll use that .NET wrapper library to create an ASP.NET MVC application for an affiliate to view, submit, and modify its shipment records.

image Note  In a real-world scenario, you would also create an admin client to allow administrators to consume the HTTP API to do elevated operations such as create affiliates, changing the shipment state, and so forth.

Building a .NET Wrapper for the HTTP API

You have your HTTP API ready to be consumed and you want your consumers to have a first-class experience consuming your API. This is where you start considering building framework- or language-specific wrappers around your HTTP API. Depending on your consumers’ portfolio, the platform you would like to invest in can vary. Also, one could argue that a well-designed HTTP API that follows the REST software architecture style doesn’t need a specific client wrapper.

Nevertheless, in the next section, you’ll design a .NET wrapper around your delivery company’s HTTP API, which will make it easy for the clients on the .NET Framework to consume the API. Inside the wrapper library, the HttpClient class will be used to talk to HTTP endpoints, and you’ll expose best practices to work with HttpClient throughout. Let’s first have a look at the separate project that we have for this library.

PingYourPackage.API.Client Project

You’ll have a separate class library project for the .NET wrapper of the HTTP API so that you can easily distribute the library as a separate assembly. You can even automate the process of this, distribute the library through NuGet if you want to, and allow you consumers to keep an eye on any library updates. The .NET library you are about to create will not have any dependency on the “Domain” (PingYourPackage.Domain) and “API” (PingYourPackage.API) projects. However, it will have a dependency on the “Model” project (PingYourPackage.API.Model) here as you’ll use the same .NET classes to represent the data transferred over the wire, which is going be a big code reuse for you. Also, the model classes will act as the contract here. You can see from Figure 7-1 that the PingYourPackage.API.Client project has a reference for the PingYourPackage.API.Model assembly.

9781430247258_Fig07-01.jpg

Figure 7-1. The PingYourPackage.API.Model assembly reference

Notice in Figure 7-1 that you have other types of references, such as System.Net.Http, which we’ll explain in the next section.

Integrating with WebApiDoodle.Net.Http.Client NuGet Package

To be able to create a .NET wrapper around the HTTP API, you’ll make use of the new HttpClient class. However, there are a few things you need to be concered with when using HttpClient, such as hitting the endpoint and deserializing the raw data asynchronously, which might be a bit tricky even if you use the C# 5.0 asynchronous language features. For instance, as we explained at the end of Chapter 2, you can easily make it possible for the consumers of your .NET API to introduce deadlocks. Besides that, there is a little bit of a performance effect to unnecessarily synchronize with current context. There is a NuGet package that makes all of this easy: WebApiDoodle.Net.Http.Client. This package integrates with the Microsoft.AspNet.WebApi.Client package. So, you can install only the WebApiDoodle.Net.Http.Client package and you’ll be good to go (see Figure 7-2).

9781430247258_Fig07-02.jpg

Figure 7-2. Instaling the WebApiDoodle.Net.Http.Client package

The most important object this NuGet package provides is the HttpApiClient<TResult> generic abstract class, which is essentially the same as HttpClient but with a bit more functionality. This class is very opinionated about the way you are going to consume the HTTP endpoints but will fit very well with what we have in mind here. It has essentially the same methods as the HttpClient but with absolutely different signatures (see Figure 7-3).

9781430247258_Fig07-03.jpg

Figure 7-3. The HttpApiClient<TResult> signature

Depending on the operation type, the return types of these methods are either Task<HttpApiResponseMessage<TResult>> or Task<HttpApiResponseMessage>. The HttpApiResponseMessage<TResult> instance will save you from the serialization work, which will be done out of the box by the package. The next section will cover how to create the client classes (ShipmentsClient and ShipmentTypesClient), and this will give you more in-depth information about the library.

Creating the ShipmentsClient and ShipmentTypesClient

Adding, removing, and manipulating the shipment records through the HTTP API are the most important concepts for this client as you are writing this library for its affiliates. The ShipmentsClient class, which implements the IShipmentsClient, will be the client class for those operations. The signature of the IShipmentsClient interface is shown in Listing 7-1.

Listing 7-1.  IShipmentsClient Interface

public interface IShipmentsClient {

    Task<PaginatedDto<ShipmentDto>> GetShipmentsAsync(
        PaginatedRequestCommand paginationCmd);
        
    Task<ShipmentDto> GetShipmentAsync(
        Guid shipmentKey);
    
    Task<ShipmentDto> AddShipmentAsync(
        ShipmentByAffiliateRequestModel requestModel);
        
    Task<ShipmentDto> UpdateShipmentAsync(
        Guid shipmentKey,
        ShipmentByAffiliateUpdateRequestModel requestModel);
        
    Task RemoveShipmentAsync(
        Guid shipmentKey);
}

This interface covers all the necessary information that will be used by an affiliate. The implementation class of the IShipmentsClient interface, ShipmentsClient, will be derived from the HttpApiClient<ShipmentDto> as all the return types can be either deserialized to ShipmentDto or PaginatedDto<ShipmentDto>, and this makes it easy to use HttpApiClient as the base class.

When you look at the constructors of the HttpApiClient class, you’ll see that all of them accept a System.Net.Http.HttpClient instance to communicate through HTTP, and this enables you to share one instance of HttpClient across the client class implementations. Also, the specified HttpClient is not going to be disposed of by the HttpApiClient instance because an HttpClient instance can be used throughout the application lifecycle safely and its methods are thread safe:

  • CancelPendingRequests
  • DeleteAsync
  • GetAsync
  • GetByteArrayAsync
  • GetStreamAsync
  • GetStringAsync
  • PostAsync
  • PutAsync
  • SendAsync

Optionally, the HttpApiClient class accepts the MediaTypeFormatter collection to deserialize the data and a MediaTypeFormatter instance to write (serialize) the data. Considering these options, our ShipmentsClient class’s constructor looks like the code shown in Listing 7-2.

Listing 7-2.  Initial Structure of the ShipmentsClient Class

public class ShipmentsClient :
    HttpApiClient<ShipmentDto>, IShipmentsClient {

    private const string BaseUriTemplate =
        "api/affiliates/{key}/shipments";
        
    private const string BaseUriTemplateForSingle =
        "api/affiliates/{key}/shipments/{shipmentKey}";
        
    private readonly string _affiliateKey;

    public ShipmentsClient(HttpClient httpClient, string affiliateKey)
        : base(httpClient, MediaTypeFormatterCollection.Instance) {

        if (string.IsNullOrEmpty(affiliateKey)) {

            throw new ArgumentException(
                "The argument 'affiliateKey' is null or empty.",
                "affiliateKey");
        }

        _affiliateKey = affiliateKey;
    }
    
    // Lines romoved for brevity
}

As you can see, the constructor of the ShipmentsClient class accepts an instance of the HttpClient object, and how this instance can be provided will be discussed in the next section. You also can see the singleton MediaTypeFormatterCollection class in Listing 7-3.

Listing 7-3.  MediaTypeFormatterCollection Singleton Class

internal sealed class MediaTypeFormatterCollection :
    ReadOnlyCollection<MediaTypeFormatter> {

    private static readonly Lazy<MediaTypeFormatterCollection> lazy =
           new Lazy<MediaTypeFormatterCollection>(() =>
               new MediaTypeFormatterCollection());

    public static MediaTypeFormatterCollection Instance { get { return lazy.Value; } }

    private MediaTypeFormatterCollection()
        : base(new List<MediaTypeFormatter> { new JsonMediaTypeFormatter() }) {
    }
}

Inside the ShipmentClient class, you have a few private helper methods to help handle the response message properly (see Listing 7-4).

Listing 7-4.  Private Helper Methods on ShipmentsClient

public class ShipmentsClient :
    HttpApiClient<ShipmentDto>, IShipmentsClient {
    
    // Lines romoved for brevity
    
    private async Task<TResult> HandleResponseAsync<TResult>(
        Task<HttpApiResponseMessage<TResult>> responseTask) {

        using (var apiResponse = await responseTask) {

            if (apiResponse.IsSuccess) {

                return apiResponse.Model;
            }

            throw GetHttpApiRequestException(apiResponse);
        }
    }

    private async Task HandleResponseAsync(
        Task<HttpApiResponseMessage> responseTask) {

        using (var apiResponse = await responseTask) {

            if (!apiResponse.IsSuccess) {

                throw GetHttpApiRequestException(apiResponse);
            }
        }
    }

    private HttpApiRequestException GetHttpApiRequestException(
        HttpApiResponseMessage apiResponse) {

        return new HttpApiRequestException(
            string.Format(ErrorMessages.HttpRequestErrorFormat,
                (int)apiResponse.Response.StatusCode,
                apiResponse.Response.ReasonPhrase),
                apiResponse.Response.StatusCode,
                apiResponse.HttpError);
    }
}

As you can see, there are two HandleResponseAsync methods and one of them is a generic method. These methods will be used for every HTTP request that you’ll make and act on the response to either return the deserialized object or throw an HttpApiRequestException (which comes with the WebApiDoodle.Net.Http.Client NuGet package). In cases where there is no message body, it will just await the response and return the Task object.

Let’s start looking at how the HTTP requests are being made. Listing 7-5 shows the GET request methods inside the ShipmentsClient.

Listing 7-5.  GET Methods on ShipmentsClient

public class ShipmentsClient :
    HttpApiClient<ShipmentDto>, IShipmentsClient {
    
     // Lines romoved for brevity

    public async Task<PaginatedDto<ShipmentDto>> GetShipmentsAsync(
        PaginatedRequestCommand paginationCmd) {

        var parameters = new {
            key = _affiliateKey,
            page = paginationCmd.Page,
            take = paginationCmd.Take };
            
        var responseTask = base.GetAsync(
            BaseUriTemplate, parameters);
            
        var shipments = await HandleResponseAsync(responseTask);
        return shipments;
    }

    public async Task<ShipmentDto> GetShipmentAsync(
        Guid shipmentKey) {

        var parameters = new {
            key = _affiliateKey,
            shipmentKey = shipmentKey };
            
        var responseTask = base.GetSingleAsync(
            BaseUriTemplateForSingle, parameters);
            
        var shipment = await HandleResponseAsync(responseTask);
        return shipment;
    }
    
     // Lines romoved for brevity
}

One of the great features of the HttpApiClient class is that it allows you to generate the URI over a .NET object. In our case in Listing 7-5, we are creating anonymous types to hold the URI parameters and pass that variable into the HttpClientApi methods. Notice that you’re retrieving the deserialized object through the HandleResponseAsync method, which will also handle the disposition of the HttpResponseMessage object.

Implementation of the ShipmentClient methods for POST, PUT, and DELETE HTTP operations will look very similar (see Listing 7-6).

Listing 7-6.  Add, Update, and Remove Methods on ShipmentsClient

public class ShipmentsClient :
    HttpApiClient<ShipmentDto>, IShipmentsClient {
    
     // Lines romoved for brevity

    public async Task<ShipmentDto> AddShipmentAsync(
        ShipmentByAffiliateRequestModel requestModel) {

        var parameters = new { key = _affiliateKey };
        var responseTask = base.PostAsync(
            BaseUriTemplate, requestModel, parameters);
            
        var shipment = await HandleResponseAsync(responseTask);
        return shipment;
    }

    public async Task<ShipmentDto> UpdateShipmentAsync(
        Guid shipmentKey,
        ShipmentByAffiliateUpdateRequestModel requestModel) {

        var parameters = new { key = _affiliateKey, shipmentKey = shipmentKey };
        var responseTask = base.PutAsync(
            BaseUriTemplateForSingle, requestModel, parameters);
            
        var shipment = await HandleResponseAsync(responseTask);
        return shipment;
    }

    public async Task RemoveShipmentAsync(Guid shipmentKey) {
        
        var parameters = new { key = _affiliateKey, shipmentKey = shipmentKey };
        var responseTask = base.DeleteAsync(BaseUriTemplateForSingle, parameters);
        await HandleResponseAsync(responseTask);
    }
    
     // Lines romoved for brevity
}

image Note  Besides the ShipmentsClient class, there is also another client class: ShipmentTypesClient. Because it has the same structure as ShipmentsClient, we won’t go into its detail here. However, you can see the implementation code inside the project’s source code.

Now you have the client wrapper, but you still have to construct an instance properly. The following section is all about this topic.

Creating the ApiClientContext

In order to construct the client classes, you need an HttpClient instance that you can pass along safely. Also, you don’t want to expose this client as a public member from the library as the library will be the only one using this. This is where the ApiClientContext class comes in handy. With the ApiClientContext class, you’ll have a fluent API to construct your client instances. Let’s look at the implementation of ApiClientContext first (see Listing 7-7) and then we’ll explain it.

Listing 7-7.  ApiClientContext Class

public sealed class ApiClientContext {

    private ApiClientContext() { }

    private static readonly Lazy<ConcurrentDictionary<Type, object>> _clients =
        new Lazy<ConcurrentDictionary<Type, object>>(() =>
            new ConcurrentDictionary<Type, object>(), isThreadSafe: true);

    private static readonly Lazy<HttpClient> _httpClient =
                new Lazy<HttpClient>(
                    () => {

                        Assembly assembly = Assembly.GetExecutingAssembly();
                        HttpClient httpClient = HttpClientFactory.Create(
                            innerHandler: new HttpClientHandler() {
                                AutomaticDecompression = DecompressionMethods.GZip });

                        httpClient.DefaultRequestHeaders.Accept.Add(
                            new MediaTypeWithQualityHeaderValue("application/json"));
                            
                        httpClient.DefaultRequestHeaders.AcceptEncoding.Add(
                            new StringWithQualityHeaderValue("gzip"));
                            
                        httpClient.DefaultRequestHeaders.Add(
                            "X-UserAgent",
                            string.Concat(
                                assembly.FullName,
                                "( ", FileVersionInfo.GetVersionInfo(
                                    assembly.Location).ProductVersion,
                                ")"
                            )
                        );

                        return httpClient;

                    }, isThreadSafe: true);

    internal ConcurrentDictionary<Type, object> Clients {

        get { return _clients.Value; }
    }

    internal Uri BaseUri { get; set; }
    internal string AuthorizationValue { get; set; }
    internal string AffiliateKey { get; set; }

    internal HttpClient HttpClient {

        get {

            if (!_httpClient.IsValueCreated) {

                InitializeHttpClient();
            }

            return _httpClient.Value;
        }
    }

    public static ApiClientContext Create(
        Action<ApiClientConfigurationExpression> action) {

        var apiClientContext = new ApiClientContext();
        var configurationExpression =
            new ApiClientConfigurationExpression(apiClientContext);

        action(configurationExpression);

        return apiClientContext;
    }

    private void InitializeHttpClient() {

        if (BaseUri == null) {
            throw new ArgumentNullException("BaseUri");
        }

        if (string.IsNullOrEmpty(AuthorizationValue)) {
            throw new ArgumentNullException("AuthorizationValue");
        }

        // Set BaseUri
        _httpClient.Value.BaseAddress = BaseUri;

        // Set default headers
        _httpClient.Value.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Basic", AuthorizationValue);
    }
}

What you have here is only a portion of the client API, but this class is the one on which you’ll build your extensions. The Create method of ApiClientContext is the only public member that you are exposing, and it’s responsible for constructing an ApiClientContext instance. The Create method accepts a parameter that is of type Action<ApiClientConfigurationExpression>, and ApiClientConfigurationExpression has public methods to allow you to set the necessary values such as the base URI of the API. See Listing 7-8 for the implementation of the ApiClientConfigurationExpression class.

Listing 7-8.  ApiClientConfigurationExpression Class

public class ApiClientConfigurationExpression {

    private readonly ApiClientContext _apiClientContext;

    internal ApiClientConfigurationExpression(
        ApiClientContext apiClientContext) {

        if (apiClientContext == null) {

            throw new ArgumentNullException("apiClientContext");
        }

        _apiClientContext = apiClientContext;
    }

    public ApiClientConfigurationExpression SetCredentialsFromAppSetting(
        string affiliateKeyAppSettingKey,
        string usernameAppSettingKey,
        string passwordAppSettingKey) {

        if (string.IsNullOrEmpty(affiliateKeyAppSettingKey)) {

            throw new ArgumentException(
                "The argument 'affiliateKeyAppSettingKey' is null or empty.",
                "affiliateKeyAppSettingKey");
        }

        if (string.IsNullOrEmpty(usernameAppSettingKey)) {

            throw new ArgumentException(
                "The argument 'usernameAppSettingKey' is null or empty.",
                "usernameAppSettingKey");
        }

        if (string.IsNullOrEmpty(passwordAppSettingKey)) {

            throw new ArgumentException(
                "The argument 'passwordAppSettingKey' is null or empty.",
                "passwordAppSettingKey");
        }

        string affiliateKey = ConfigurationManager.AppSettings[affiliateKeyAppSettingKey];
        string username = ConfigurationManager.AppSettings[usernameAppSettingKey];
        string password = ConfigurationManager.AppSettings[passwordAppSettingKey];

        if (string.IsNullOrEmpty(affiliateKey)) {

            throw new ArgumentException(
                string.Format(
                    "The application setting '{0}' does not exist or its value is null.",
                    affiliateKeyAppSettingKey));
        }

        if (string.IsNullOrEmpty(username)) {

            throw new ArgumentException(
                string.Format(
                    "The application setting '{0}' does not exist or its value is null.",
                    usernameAppSettingKey));
        }

        if (string.IsNullOrEmpty(password)) {

            throw new ArgumentException(
                string.Format(
                    "The application setting '{0}' does not exist or its value is null.",
                    passwordAppSettingKey));
        }

        _apiClientContext.AffiliateKey = affiliateKey;
        _apiClientContext.AuthorizationValue =
            EncodeToBase64(string.Format("{0}:{1}", username, password));

        return this;
    }

    public ApiClientConfigurationExpression ConnectTo(string baseUri) {

        if (string.IsNullOrEmpty(baseUri)) {

            throw new ArgumentNullException("baseUri");
        }

        _apiClientContext.BaseUri = new Uri(baseUri);

        return this;
    }

    private static string EncodeToBase64(string value) {

        byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(toEncodeAsBytes);
    }
}

As you can see, there are not many methods exposed publicly, but if your API client needs any configuration points, this is the place where you can expose those. Also notice that all public methods return the class instance itself to expose a fluent API so you can chain method calls.

You have all the infrastructure for constructing your clients but you haven’t had any implementation performing these operations. Let’s perform these operations through extension methods on the ApiClientContext class (see Listing 7-9), which will retrieve the client instances through the internal ConcurrentDictionary that is on the ApiClientContext class.

Listing 7-9.  ApiClientContext Extensions

public static class ApiClientContextExtensions {

    public static IShipmentsClient GetShipmentsClient(
        this ApiClientContext apiClientContext) {

        return apiClientContext.GetClient<IShipmentsClient>(() =>
            new ShipmentsClient(
                apiClientContext.HttpClient,
                apiClientContext.AffiliateKey));
    }

    public static IShipmentTypesClient GetShipmentTypesClient(
        this ApiClientContext apiClientContext) {

        return apiClientContext.GetClient<IShipmentTypesClient>(() =>
            new ShipmentTypesClient(apiClientContext.HttpClient));
    }

    internal static TClient GetClient<TClient>(
        this ApiClientContext apiClientContext, Func<TClient> valueFactory) {

        return (TClient)apiClientContext.Clients.GetOrAdd(
            typeof(TClient),
            k => valueFactory());
    }
}

Here you’re taking advantage of the GetOrAdd method of the ConcurrentDictionary, which gives you the ability to retrieve the object if there is already one with the same key or add a new one if there is none. Because the client methods and the HttpClient methods are thread safe, there’s nothing to prevent you from having single instances of the client classes.

Now the HTTP API’s .NET wrapper is ready, so you can use it on your web application to consume the data through the HTTP API.

Building a Web Client with ASP.NET MVC

This section will use that .NET wrapper library you just created to create an ASP.NET MVC application for an affiliate to view, submit, and modify its shipment records. Note that this is a fairly simple web application and is not meant to be a guide on how to build your web applications with ASP.NET MVC. The only purpose of this web application is to show how to consume the HTTP API through the .NET wrapper library.

PingYourPackage.API.Client.Web Project

To create your client web application, you have to add a new ASP.NET MVC project named PingYourPackage.API.Client.Web. We choose to use the default ASP.NET MVC template for this application as the UI design of the application is not a big concern here. Besides the preinstalled NuGet packages on the ASP.NET MVC project template, we have installed the following NuGet packages into our PingYourPackage.API.Client.Web:

  • Autofac.Mvc4
  • WebApiDoodle.Net.Http.Client

These packages have dependencies to other packages, and they got pulled into the project automatically. In addition, we added the PingYourPackage.API.Client project reference in our web project.

Registering the Client Classes Through an IoC Container

In the ASP.Net MVC application, you’ll leverage the ASP.NET MVC’s dependency resolver extensibility as we did in our Web API implementation. Both implementations are quite similar but registration points are obviously different.

Let’s look at the code in Listing 7-10 for our ASP.NET MVC controller’s constructor.

Listing 7-10.  ASP.NET MVC Controller and Its Constructor

public class HomeController : Controller {

    private const int DefaultPageSize = 2;
    private readonly IShipmentsClient _shipmentsClient;
    private readonly IShipmentTypesClient _shipmentTypesClient;

    public HomeController(
        IShipmentsClient shipmentsClient,
        IShipmentTypesClient shipmentTypesClient) {

        _shipmentsClient = shipmentsClient;
        _shipmentTypesClient = shipmentTypesClient;
    }
    
    // Lines romoved for brevity
}

The controller’s constructor accepts two parameters that are of type IShipmentsClient and IShipmentTypesClient. By default, the ASP.NET MVC infrastructure cannot create this controller. However, you can set a custom dependency resolver for ASP.NET MVC and set it through the static DependencyResolver.SetResolver method. We used the Autofac ASP.NET MVC implementation (see Listing 7-11).

Listing 7-11.  Internal AutofacMvc Class to Register Client Instances

internal static class AutofacMvc {

    internal static void Initialize() {

        var builder = new ContainerBuilder();
        DependencyResolver.SetResolver(
            new AutofacDependencyResolver(RegisterServices(builder)));
    }

    private static IContainer RegisterServices(ContainerBuilder builder) {

        builder.RegisterControllers(typeof(MvcApplication).Assembly);

        ApiClientContext apiClientContex =
            ApiClientContext.Create(cfg =>
                cfg.SetCredentialsFromAppSetting(
                    "Api:AffiliateKey", "Api:Username", "Api:Password")
                            .ConnectTo("https://localhost:44306"));

        // Register the clients
        builder.Register(c => apiClientContex.GetShipmentsClient())
            .As<IShipmentsClient>().InstancePerHttpRequest();

        builder.Register(c => apiClientContex.GetShipmentTypesClient())
            .As<IShipmentTypesClient>().InstancePerHttpRequest();

        return builder.Build();
    }
}

Looking at the AutofacMvc class, you can see that the RegisterServices method does the registration of the HTTP client wrappers. First, you create the ApiClientContext class through which you’ll get the client instances. Then you use the SetCredentialsFromAppSetting method to set the credentials for the HTTP API, and these credentials are stored as appSettings inside the web.config file (see Listing 7-12).

Listing 7-12.  AppSetting Values for API Credentials Inside Web.config

<appSettings>
    
    // Lines romoved for brevity
    
    <add key="Api:Username" value="CompanyA" />
    <add key="Api:Password" value="CompanyAPass" />
    <add key="Api:AffiliateKey" value="041f765a-1019-4956-b610-370d05be95ac" />
  </appSettings>

Then, the clients are registered per request instance using the extension methods that were built for ApiClientContext. Finally, we called the static Initialize method of the AutofacMvc class during the application startup phase (see Listing 7-13).

Listing 7-13.  Application_Start Method Inside Global.asax.cs

protected void Application_Start() {

    AutofacMvc.Initialize();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

The IoC container registration is now completed and we can move onto building the MVC controller to consume the HTTP API.

Implementing the MVC Controller

For this sample, we have one ASP.NET MVC controller named HomeController, and this controller will work with the IShipmentsClient and IShipmentTypesClient implementations, which will be provided through the dependency resolver that have been put in place. The other important part to mention is the asynchronous nature of our calls. Because the API wrappers are fully asynchronous, you’ll make the network calls inside the ASP.NET MVC controller actions asynchronously thanks to MVC’s support of the task-based asynchronous pattern. You can see the Index action method in Listing 7-14, which gets the paginated list of shipment records for the affiliate.

Listing 7-14.  MVC Controller Index Method to Get Paginated Shipments Collection

public class HomeController : Controller {

    // Lines romoved for brevity

    public async Task<ViewResult> Index(int page = 1) {

        PaginatedDto<ShipmentDto> shipments =
            await _shipmentsClient.GetShipmentsAsync(
                new PaginatedRequestCommand(page, DefaultPageSize));

        return View(shipments);
    }
    
    // Lines romoved for brevity
}

Nearly all action methods are similar to the Index action method. You make a call to the HTTP API and get back the results. Then, you process the result in the ASP.NET MVC action. You can see a slightly different action method implementation for creating a new shipment record in Listing 7-15.

Listing 7-15.  MVC Controller Create Action to Handle POST Request Against the HTTP API

public class HomeController : Controller {

    // Lines romoved for brevity

    [HttpGet]
    public async Task<ViewResult> Create() {

        await GetAndSetShipmentTypesAsync();
        return View();
    }

    [HttpPost]
    [ActionName("Create")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Create_Post(
        ShipmentByAffiliateRequestModel requestModel) {

        if (ModelState.IsValid) {

            ShipmentDto shipment =
                await _shipmentsClient.AddShipmentAsync(requestModel);

            return RedirectToAction("Details", new { id = shipment.Key });
        }

        await GetAndSetShipmentTypesAsync();
        return View(requestModel);
    }
    
    private async Task GetAndSetShipmentTypesAsync() {

        PaginatedDto<ShipmentTypeDto> shipmentTypes =
            await _shipmentTypesClient.GetShipmentTypesAsync(new PaginatedRequestCommand(1, 50));

        ViewBag.ShipmentTypes = shipmentTypes.Items.Select(x => new SelectListItem() {
            Text = x.Name,
            Value = x.Key.ToString()
        });
    }

    // Lines romoved for brevity
}

We skipped other action method implementations here as they have nearly the same structure. However, you can find the implementation of those action methods inside the application’s source code. With a bit more work on the ASP.NET MVC Razor views, you obtain the results as shown in Figure 7-4.

9781430247258_Fig07-04.jpg

Figure 7-4. Home page of the web client

image Note  So far, we haven’t shown how we host our Web API application because we have a dedicated chapter on this (Chapter 17). In that chapter, we’ll show how you can host this application and get it up and running. For this demonstration, we used a simple ASP.NET host (also known as Web Host), which you can find the code for in the sample application.

Now you can see that we consumed the HTTP API successfully and got the paginated list of shipments data. You can also see the details of a shipment record by visiting the details page of a shipment (see Figure 7-5).

9781430247258_Fig07-05.jpg

Figure 7-5. Shipment details page of the web client

When you run the application locally, you have the chance to create a new shipment record and edit or delete an existing one.

Summary

In the three sample application chapters, we wanted to dig through all the steps of building an ASP.NET Web API application. We explained how to design the application business layer and the API structure in Chapter 5. Then, in Chapter 6, we explored the details of implementing the ASP.NET Web API application itself. In this chapter, we focused on creating a client application that can consume the HTTP API with a custom-written wrapper. We hope that these three chapters have given you an idea on how to go ahead and build an API layer with ASP.NET Web API and that you are now ready to dive into each layer of ASP.NET Web API architecture in the following chapters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.226.120