I am wondering whether there is a way to avoid repeating myself in passing Request.Headers
into every service method?
[HttpGet]
[Route("accounts({id:guid})")]
[Route("accounts")]
public async Task<HttpResponseMessage> GetAccount()
{
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var response = await _accountService.GetAccount(query, Request.Headers);
return response;
}
[HttpGet]
[Route("accounts/{id:guid}")]
public async Task<HttpResponseMessage> GetAccountByID(Guid id)
{
var query = "accounts(" + id + ")";
var response = await _accountService.GetAccount(query, Request.Headers);
return response;
}
[HttpPatch]
[Route("accounts/{id:guid}")]
public async Task<HttpResponseMessage> UpdateAccount([FromBody] JObject account, Guid id)
{
var response = await _accountService.Update(account, id, Request.Headers);
return response;
}
[HttpPost]
[Route("accounts")]
public async Task<HttpResponseMessage> CreateAccount([FromBody] JObject account)
{
return await _accountService.Create(account, Request.Headers);
}
The client code is as follows:
public async Task<HttpResponseMessage> GetAccount(string query)
{
var response = Client.Instance.GetAsync(Client.Instance.BaseAddress + query);
var responseType = response.Result.StatusCode;
if (responseType == HttpStatusCode.NotFound)
{
return new HttpResponseMessage
{
StatusCode = responseType
};
}
return await response;
}
public async Task<HttpResponseMessage> Create(JObject account)
{
var request = new HttpRequestMessage(HttpMethod.Post, Client.Instance.BaseAddress + "accounts")
{
Content = new StringContent(account.ToString(), Encoding.UTF8, "application/json")
};
var response = await Client.Instance.SendAsync(request);
var responseType = response.StatusCode;
if (responseType == HttpStatusCode.BadRequest)
{
return new HttpResponseMessage
{
StatusCode = responseType
};
}
var uri = new Uri(response.Headers.GetValues("OData-EntityId").First());
var content = await Client.Instance.GetAsync(uri);
if (content.StatusCode == HttpStatusCode.BadRequest)
{
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest
};
}
return new HttpResponseMessage
{
Content = content.Content,
StatusCode = HttpStatusCode.NoContent == responseType ? HttpStatusCode.Created : responseType
};
}
public async Task<HttpResponseMessage> Update(JObject account, Guid id)
{
var request = new HttpRequestMessage(new HttpMethod("PATCH"), Client.Instance.BaseAddress + "accounts(" + id + ")")
{
Content = new StringContent(account.ToString(), Encoding.UTF8, "application/json")
};
var updateRequest = await Client.Instance.SendAsync(request);
var responseType = updateRequest.StatusCode;
if (responseType == HttpStatusCode.BadRequest)
{
return new HttpResponseMessage
{
StatusCode = responseType
};
}
var uri = new Uri(updateRequest.Headers.GetValues("OData-EntityId").Single());
var updateResponse = await Client.Instance.GetAsync(uri);
return updateResponse;
}
In my attempts to refactor, a very nice suggestion was to merge the service and controller layers:
[HttpGet]
[Route("accounts({id:guid})")]
[Route("accounts")]
public async Task<HttpResponseMessage> GetAccount (HttpRequestMessage Request) {
//at the line below is where i want to send the same headers that were passed in originally at step 1
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var headers = Request.Headers;
var url = Client.Instance.BaseAddress + query;
//create new request and copy headers
var proxy = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in headers) {
proxy.Headers.Add(header.Key, header.Value);
}
var response = await Client.Instance.SendAsync(proxy);//This is an assumption.
var responseType = response.StatusCode; //Do not mix blocking calls. It can deadlock
if (responseType == HttpStatusCode.NotFound)
return new HttpResponseMessage {
StatusCode = responseType
};
return response;
}
However, that does not solve my concern of violating DRY.
I then tried a more functional approach, which may succeed eventually, but it may need to be more robust. It will need to handle different HTTP verbs. As you can see the functions are all static. There are no dependencies, and almost no state mutation:
public async Task<HttpResponseMessage> FunctionalGetAccount(HttpRequestMessage globalRequest)
{
var request = new HttpRequest(globalRequest);
var query = CreateQuery(request);
var url = CreateURL(query);
var proxy = CreateProxy(url);
var headers = GetHeaders(request);
AddHeaders(headers, proxy);
var response = await AwaitResponse(proxy);
var httpStatusCode = MapHttpStatusCode(response.StatusCode);
var newHttpResponse = CreateResponse(response, httpStatusCode);
return newHttpResponse;
}
private static HttpStatusCode MapHttpStatusCode(HttpStatusCode input)
{
//based on some criteria TBD
return HttpStatusCode.NotFound;
}
private static HttpResponseMessage CreateResponse(HttpResponseMessage response, HttpStatusCode newStatusCode)
{
//should be made immutable
//update the status code to newStatusCode
var updatedResponse = response;
//updatedResponse.StatusCode = newStatusCode;
//logic TBD
return updatedResponse;
}
private static async Task<HttpResponseMessage> AwaitResponse(HttpRequest proxy)
{
foreach (var header in proxy.Request.Headers)
{
Client.Instance.DefaultRequestHeaders.Add(header.Key, header.Value);
}
var response = Client.Instance.SendAsync(proxy.Request);
return await response;
}
private static void AddHeaders(HttpRequestHeaders headers, HttpRequest proxy)
{
foreach (var header in headers)
{
proxy.Request.Headers.Add(header.Key, header.Value);
}
}
private static HttpRequestHeaders GetHeaders(HttpRequest request)
{
var headers = request.Request.Headers;
return headers;
}
private static HttpRequest CreateProxy(string url)
{
var proxy = new HttpRequest(new HttpRequestMessage(HttpMethod.Get, url));
return proxy;
}
private static string CreateURL(string query)
{
var url = Client.Instance.BaseAddress + query;
return url;
}
private static string CreateQuery(HttpRequest Request)
{
var query = Request.Request.RequestUri.AbsolutePath.Split('/').Last() + Request.Request.RequestUri.Query;
return query;
}
Though not necessarily central to the question, this is how I've defined HttpRequest
:
public class HttpRequest : ValueObject<HttpRequest>
{
public virtual HttpRequestMessage Request { get; }
public HttpRequest(HttpRequestMessage request)
{
Request = Cloner.CloneHttpRequestMessageAsync(request).Result;
}
protected override bool EqualsCore(HttpRequest other)
{
return other.Request.Content == Request.Content
&& other.Request.Method == Request.Method
&& other.Request.RequestUri == Request.RequestUri;
}
protected override int GetHashCodeCore()
{
return ((Request.Method.GetHashCode() * 397) ^ Request.Content.GetHashCode()) ^ Request.RequestUri.GetHashCode();
}
}
How do I avoid having to specify every time that I want to pass Request.Headers to every serivce method?
As a side note, the functional approach has been inspired mostly by Vladimir Khorikov as well as Ralf Westphal.