Applying Single Responsibility Principle when calling a remote Web API

Introduction

A requirement of a certain client was as follows

  • Call a third-party web API from an ASP.NET MVC Controller
  • The calling of the web API should work as follows
    • Check if the token sent with the call is valid
    • if the token is not valid, generate a new token and update a database field where token is stored
    • send the token with the call to authorize the request operation

This requirement was asked by the customer shortly after completing a course talking about software architecture and design principles. So it was the first practical implementing of the principles learned

Implementation

  • The design of the solution tried to stick to the Single Responsibility Principle.
  • So when splitting the requirements above into business classes, it resulted in
    • The responsibility of the controller action method is to call the API only.It has nothing to do with
      • Creating an http client object
      • Check if the authorization parameters are passed
    • The responsibility of the class calling the API only is to call the web API only .It has nothing to do with
      • checking for the token
      • Check for token expiration, generating and storing tokens
    • So the above responsibilities will be moved to a third class
  • So the outline of the classes involved was as follows (code details are omitted for
    1. brevity
    2. coding standards
    3. differences among companies and individuals
    4. Web API belongs to the client or third-party
    )
    1. The Main API Caller class
         public abstract class MainAPICaller
          {
             private TokenManager tokenManager;
      
      
              protected async Task PrepareToken()
                  //this method was placed here
                 //It may be called as needed(i.e If the web api caller needs to be authorized)
              
              {
                  tokenManager = new TokenManager();
                  tokenManager.ReadTokenValueFromDb();
                  if (await tokenManager.CheckIfTokenExpired())
                  {
                      await tokenManager.GenerateNewToken();
      
                  }
                  Token = tokenManager.Token;
      
              }
      
      
              protected string _apiUrl = "";
              protected string Token = "";
             
      
              //each class implements its call to the API
              //providing parameters and request params as needed
              public abstract Task CallAPI();
      
              public HttpResponseMessage response { get; set; }
      
      
      
      
          }
      }
                  
    2. a sample class implementing the main API caller class
      				 public class SampeAPICaller : API.MainAPICaller
          { 
          public SampeAPICaller()
          {
      
              _apiUrl = "..";
      
          }
          
          
          public string Number { get; set; }//the parameters forming the query string go here 
      public override async Task CallAPI()
          {
      			//this line need not to be called if there are not special authorization requirements
                  await base.PrepareToken();
      			
                  var httpClient = new HttpClient();
                  httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
      
                  var json = JsonConvert.SerializeObject(new
                  {
                      Number = Number
                  });
             
                
                 
                  HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, _apiUrl + "?Number="+Number);
      				//this line need not to be called if there are not special authorization requirements
                  request.Headers.Authorization = new AuthenticationHeaderValue("Token", Token);
      
                this.response = await httpClient.SendAsync(request);
      
                  var responeData = await response.Content.ReadAsStringAsync();
                  return responeData;
              }
      
          
      }
      			
    3. Invoking the api caller in the controller class
      			  public class RegistrarsController : Controller
      			  {
      					  public async Task CallTelephoneVerifyFunction(string Number)
      				{
      					TelephoneCheckAPICaller telephoneCheckAPICaller = new TelephoneCheckAPICaller();
      					telephoneCheckAPICaller.Number = Number;
      
      					string responeData;
      					try
      					{
      							responeData = await telephoneCheckAPICaller.CallAPI();
      
      						return Json(new { success = telephoneCheckAPICaller.response, data = responeData });
      
      					}
      					catch (Exception ex)
      					{
      						return Json(new { success = false, data = ex.Message });
      					}
      
      				}
      			}
      			

You can note the following from the code

  • if a certain web API does not have any authorization requirement, it should only ignore calling preparetoken method defined in the abstract class. Nothing changed in the code of the MVC Controller
  • if a certain web api is moved to another url,just need to change url property in the caller class.Nothing changed in the code of the MVC Controller

So the components appear to be loosely coupled as the principle aims

Conclusion and Points of Interest

  • This division has been totally created by me and neither suggested nor copied from any body or any where
  • This is the first practical example on which I have applied the design principle(s) mentioned. So feedbacks are mostly welcomed
  • Comments

    Popular posts from this blog

    Validate that a certain checkbox is checked before submit(ASP.NET MVC)

    An example of a (LINQ) approach to solve recurring problems

    How to validate an input against a changing validation requirements