I have a simple c# mvc client that calls an external api..here is my attempt
public class ProductController : Controller
{
private readonly IProductService _productService;
public ProductController(IProductService quizService)
{
_productService = quizService;
}
public ActionResult List()
{
var viewModel = _productService.GetList(); //gets all
return View("List", viewModel);
}
public ActionResult Details(string id)
{
var viewModel = _productService.GetDetails(id); //get detail
return View("Details", viewModel);
}
}
Below is the implementation details of IProductService
public interface IProductService
{
List<ProductListResponse> GetList();
ProductDetailResponse GetDetails(string id);
}
public class ProductService : IProductService
{
private readonly IClient _httpClient;
private readonly IConfigurationService _configurationService;
private const string ExternalApiBaseUrl = "ExternalApiBaseUrl";
public ProductService(IClient httpClient, IConfigurationService configurationService)
{
_httpClient = httpClient;
_configurationService = configurationService;
}
public List<ProductListResponse> GetList()
{
var baseUrl = _configurationService.Get(ExternalApiBaseUrl);
var restClient = _httpClient.GetClient(baseUrl);
var restRequest = _httpClient.GetRequest();
var products = _httpClient.ExecuteRequestToGetList(restClient, restRequest) as IList<ProductBase>;
if (products != null)
{
return products.Select(prod=> new ProductListResponse
{
Id = prod.Id,
OnSaleDate = prod.EndDate,
Name = prod.Name,
Popularity = prod.Popularity
}).ToList();
}
return new List<ProductListResponse>();
}
public ProductDetailResponse GetDetails(string id)
{
var baseUrl = _configurationService.Get(ExternalApiBaseUrl);
var restClient = _httpClient.GetClient(baseUrl);
var clientResourceRequest = new ClientResourceRequest() { ResourceUri = "{id}", ResourceValueDictionary = new Dictionary<string, string>() { { "id", id } } };
var restRequest = _httpClient.GetRequestWithResource(clientResourceRequest);
var prodDetail = _httpClient.ExecuteRequestToGetDetails(restClient, restRequest) as ProductDetail;
var response = new ProductDetailResponse();
if (prodDetail == null) return response;
response.Category = prodDetail.Category;
response.Description = prodDetail.Description;
response.EndDate = prodDetail.EndDate;
response.Id = prodDetail.Id;
response.Name = prodDetail.Name;
response.Popularity = prodDetail.Popularity;
response.StartDate = prodDetail.StartDate;
return response;
}
}
I have abstracted the calls to REST api in a wrapper like so
public interface IClient
{
object GetClient(string baseUrl);
object GetRequest();
object GetRequestWithResource(ClientResourceRequest resourceValueRequest);
object ExecuteRequestToGetDetails(object client, object request);
object ExecuteRequestToGetList(object client, object request);
}
public class ProductRestClient : IClient
{
public object GetClient(string baseUrl)
{
return new RestClient(baseUrl);
}
public object GetRequest()
{
return new RestRequest(Method.GET);
}
public object GetRequestWithResource(ClientResourceRequest resourceValueRequest)
{
var request = new RestRequest(resourceValueRequest.ResourceUri, Method.GET);
foreach (var item in resourceValueRequest.ResourceValueDictionary)
{
request.AddUrlSegment(item.Key, item.Value);
}
return request;
}
public object ExecuteRequestToGetDetails(object client, object request)
{
var restClient = client as RestClient;
var restRequest = request as RestRequest;
var result = restClient.Execute<ProductDetail>(restRequest);
return result.Data;
}
public object ExecuteRequestToGetList(object client, object request)
{
var restClient = client as RestClient;
var restRequest = request as RestRequest;
var result = restClient.Execute<List<ProductBase>>(restRequest);
return result.Data;
}
}
The REST client wrapper request object look like so
public class ClientResourceRequest
{
public string ResourceUri{ get; set; }
public Dictionary<string, string> ResourceValueDictionary { get; set; }
}
the domain entitys are like so (used in restclient implementation and service but never made available to controllers)
public class ProductBase : IEntityBase
{
public DateTime EndDate { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Popularity { get; set; }
public DateTime StartDate { get; set; }
}
public class ProductDetail : ProductBase, IEntityBase
{
public string Category { get; set; }
public string Description { get; set; }
public string NumberOfPlays { get; set; }
}
the controllers are only aware of these request response objects (sort of viewmodels)
public class ProductListResponse
{
public DateTime OnSaleDate { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Popularity { get; set; }
}
public class ProductDetailResponse
{
public string Category { get; set; }
public string Description { get; set; }
public DateTime EndDate { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Popularity { get; set; }
public DateTime StartDate { get; set; }
}
Any feedback on the quality and how extensible this code is, is much appreciated. Issues such as
if the endpoint from the API changes and starts using another parameter, how do I pre-empt for that change and code accordingly.
My rest client wrapper has domains (productbase/productdetails), anyway I can avoid using them?
Is it fine to return objects in my custom rest client calls? Should they be strongly typed? If yes how do i handle errors?
Plus if you could suggest a better way of dealing with these api calls to make them more performant, that would be great.