ApiClient and SecureApiClient - denmitchell/EDennis.AspNetCore.Base GitHub Wiki

ApiClient and SecureApiClient are implementation of the Typed Client Pattern with some enhancements.

In the typed client pattern, an HttpClient instance is injected into a class that exposes methods that abstract away HTTP communications. The typed client pattern is analogous to the repository pattern, which abstracts away database operations.

SecureApiClient is used to access an API that has been secured by an Identity Server. ApiClient should be used in other situations. Both classes can be extended to provide additional base functionality.

ApiClient

Besides implementing the typed HttpClient pattern in an abstract base class, ApiClient provides two additional helpful features:

  • It sets the BaseAddress property from Configuration -- allowing test instances to point to different URLs more easily.
  • It sets the DefaultHeaders property from a request-scoped object (ScopeProperties) -- allowing a parent HTTP request to propagate selected headers to a child HTTP request. This can be helpful for
    • Propagating a User Name from a main application to child web APIs (a built-in feature of EDennis.AspNetCore.Base)
    • Building a "Client Trace" analog of a stack trace across a succession of clients and called APIs (a built-in feature of EDennis.AspNetCore.Base)
    • During testing, propagating a header that tells a child web API which in-memory database to use (a built-in feature of EDennis.AspNetCore.Base)

Constructor

The ApiClient constructor accepts three arguments:

  • HttpClient -- the System.Net.Http.HttpClient instance generated (behind the scenes) by the HttpClientFactory
  • IConfiguration -- the Microsoft.Extensions.Configuration.IConfiguration singleton instance holding all app configuration data.
  • ScopeProperties -- the EDennis.AspNetCore.Base.ScopeProperties scoped instance holding data for the given request. (NOTE: To propagate headers to an ApiClient through ScopeProperties, the headers must be added to OtherProperties as a Dictionary<string,List<KeyValuePair<string,StringValues>>>, where the key of the outer dictionary is the name of the ApiClient class, and the value of the outer dictionary is the collection of headers.)
public ApiClient(HttpClient httpClient, IConfiguration config, ScopeProperties scopeProperties)

Properties

For convenience, ApiClient exposes all injected parameters as properties.

Property Description
public HttpClient HttpClient { get; set; } The HttpClient generated by the HttpClientFactory
public IConfiguration Configuration { get; set; } The singleton configuration object
public ScopeProperties ScopeProperties { get; set; } The request-scoped dictionary of custom data

Methods

There are no methods in this base class (beyond those provided by Object). The developer should create methods that abstract away HTTP requests made by the client. The methods could include basic Get, Post/Create, Put/Update, and Delete methods and/or methods that go beyond basic record operations.

Importantly, while HTTP request abstraction is desirable, the developer should take care to handle exceptional HTTP status code results in an appropriate way. EDennis.AspNetCore.Base provides an ObjectResult class that bundles the response object (HTTP response body) and the HTTP status code (both as an enum and an int value). This class could be used as a return object in those cases when the calling controller needs to propagate the status code from the child API to the parent request.

Configuration

IConfiguration

All APIs are configured in an "Apis" section, where the key is the name of the ApiClient class. The SolutionName, ProjectName, and BaseAddress should be included. When using ApiLauncher during development/testing, the value of BaseAddress must be null because ApiLauncher will dynamically assign its address.

Example appsettings.Development.json:

{
  "Apis": {
    "InternalApi1": {
      "SolutionName": "EDennis.AspNetCore.Base",
      "ProjectName": "EDennis.Samples.Hr.InternalApi1",
      "BaseAddress": null,
      "Secret": "secret"
    },
    "InternalApi2": {
      "SolutionName": "EDennis.AspNetCore.Base",
      "ProjectName": "EDennis.Samples.Hr.InternalApi2",
      "BaseAddress": null,
      "Secret": "secret"
    }
  }
}

Startup.ConfigureServices

The ConfigureServices method should setup dependency injection for each of the ApiClients. It should also explicitly exclude setup of controllers from referenced projects.

   // note: for testing scenarios, include setup code for ApiLauncher
   // at the top of the ConfigureServices section.

   // Ensure that controllers of referenced projects are not exposed directly to the 
   // parent application; otherwise, you will need to setup dependency injection
   // for all of the referenced controllers' dependencies.
   // (NOTE: the example below uses aliases ("A," and "B") in the 
   //  using statements for the referenced project's namespace.)
   services.AddMvc()
       .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
       .ExcludeReferencedProjectControllers<A.Startup>()
       .ExcludeReferencedProjectControllers<B.Startup>();

  // Use helper extension method for adding one or more ApiClients.  
  // This helper method also sets up dependency injection for ScopeProperties.
  services.AddApiClients<InternalApi1,InternalApi2>();

SecureApiClient

SecureApiClient extends ApiClient by automatically handling security with an Identity Server. Behind the scenes, SecureApiClient uses a SecureTokenCache to hold instances of unexpired access tokens, which are used to access secured APIs. SecureApiClient requires more constructor parameters and minor additional configuration, but it is otherwise equivalent in setup to ApiClient.

Constructor

The SecureApiClient constructor accepts six arguments:

  • HttpClient -- the System.Net.Http.HttpClient instance generated (behind the scenes) by the HttpClientFactory
  • IConfiguration -- the Microsoft.Extensions.Configuration.IConfiguration singleton instance holding all app configuration data.
  • ScopeProperties -- the EDennis.AspNetCore.Base.ScopeProperties scoped instance holding data for the given request.
  • ApiClient identityServerApiClient -- a reference to the ApiClient for the Identity Server
  • SecureTokenCache -- a singleton holding access tokens keyed by API name
  • IHostingEnvironment -- the built-in hosting environment object for ASP.NET Core applications
public SecureApiClient(HttpClient httpClient, IConfiguration config, 
    ScopeProperties scopePropertiesApiClient identityServerApiClient,
    SecureTokenCache secureTokenCache, IHostingEnvironment hostingEnvironment)
        : base(httpClient, config, scopeProperties)

Configuration

IConfiguration

As with ApiClient, all APIs are configured in an "Apis" section, where the key is the name of the ApiClient class. For SecureApiClient, one of the configured APIs must be an Identity Server. The configuration for the Identity Server must include a property named "IdentityServerSecret," which represents the password for the client. All other configured APIs must have a property named "Scopes," which holds a string array of all scopes for the API resource that are requested by the client.

{
  "Apis": {
    "InternalApi1": {
      "SolutionName": "EDennis.AspNetCore.Base",
      "ProjectName": "EDennis.Samples.Hr.InternalApi1",
      "BaseAddress": null,
      "Secret": "secret",
      "Scopes": [
        "EDennis.Samples.Hr.InternalApi1.Employee.CreateEmployee",
        "EDennis.Samples.Hr.InternalApi1.Employee.GetEmployee",
        "EDennis.Samples.Hr.InternalApi1.Employee.GetEmployees"
      ]
    },
    "InternalApi2": {
      "SolutionName": "EDennis.AspNetCore.Base",
      "ProjectName": "EDennis.Samples.Hr.InternalApi2",
      "BaseAddress": null,
      "Secret": "secret",
      "Scopes": [
        "EDennis.Samples.Hr.InternalApi2"
      ]
    },
    "IdentityServer": {
      "SolutionName": "EDennis.AspNetCore.Base",
      "ProjectName": "IdentityServer",
      "BaseAddress": null,
      "Secret":  null
    }
  }
}

Startup.ConfigureServices

The ConfigureServices method should setup dependency injection for each of the ApiClients and SecureApiClients. It should also explicitly exclude setup of controllers from referenced projects.

   // note: for testing scenarios, include setup code for ApiLauncher
   // at the top of the ConfigureServices section.

   // Ensure that controllers of referenced projects are not exposed directly to the 
   // parent application; otherwise, you will need to setup dependency injection
   // for all of the referenced controllers' dependencies.
   // (NOTE: the example below uses aliases ("I," "A," and "B") in the 
   //  using statements for the referenced project's namespace.)
   services.AddMvc()
       .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
       .ExcludeReferencedProjectControllers<I.Startup>()
       .ExcludeReferencedProjectControllers<A.Startup>()
       .ExcludeReferencedProjectControllers<B.Startup>();

  // Use helper extension method for adding one or more ApiClients and/or
  // SecureApiClients.  This helper method also sets up dependency
  // injection for ScopeProperties and SecureTokenCache.
  services.AddApiClients<IdentityServer,InternalApi1,InternalApi2>();
⚠️ **GitHub.com Fallback** ⚠️