4

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.

Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062
  • This can be accomplished with dependency injection by registering your service in the container in the same place you pull the request header values. Very similar to https://stackoverflow.com/questions/39459705/how-to-inject-httpheader-value-in-controller – Tim Smojver Jul 27 '17 at 01:46
  • 1
    This may veer into the realm of opinion, but if your service classes **need** to access the headers, then the controller **should** pass them every time. I don't see this as useless repetition, but rather being very explicit about the dependencies of the service class. – Nate Barbettini Jul 27 '17 at 02:05
  • service should have no knowledge of headers – Alex Gordon Jul 27 '17 at 03:20

1 Answers1

2

One possibility would be to create a service to extract the current Request.

If hosting it in IIS, then the ASP pipeline stores the Web API message object on the current HttpContext.

Once that exists, you can access it via

HttpContext.Current.Items["MS_HttpRequestMessage"]

Source: How to access the current HttpRequestMessage object globally?

With that a service can be made. Let's say something like

public interface IHttpRequestMessageAccessor {
    HttpRequestMessage Request { get; }
}

and implemented

public class HttpRequestMessageAccessor : IHttpRequestMessageAccessor {
    const string MS_HttpRequestMessage = "MS_HttpRequestMessage";
    public HttpRequestMessage Request {
        get {
            HttpRequestMessage request = null;
            if (HttpContext.Current != null && HttpContext.Current.Items.Contains(MS_HttpRequestMessage)) {
                request = HttpContext.Current.Items[MS_HttpRequestMessage] as HttpRequestMessage;
            }
            return request;
        }
    }
}

This service can now be injected explicitly into any dependent services. Just make sure the abstraction and its implementation are registered in the composition root into your DI framework.

Hypothetical example

public class AccountService {
    private readonly IHttpRequestMessageAccessor accessor;

    public AccountService(IHttpRequestMessageAccessor accessor) {
        this.accessor = accessor;
    }

    public async Task<HttpResponseMessage> FunctionalGetAccount() {
        var globalRequest = accessor.Request;
        var request = new HttpRequest(globalRequest);

        //...code removed for brevity

        var newHttpResponse = CreateResponse(response, httpStatusCode);
        return newHttpResponse;
    }
}

Do note that due to the nature of the flow of how the request is created the request is only available within the scope of a request action. That is to say It is not available in the constructor of an ApiController as the request would not have been created as yet.

UPDATE: Self Hosting

If this is not being hosted on IIS then access to the HttpContext is not going to help.

An idea that can be tired is to use a delegating handler early in the pipeline to grab the incoming request and expose it for access globally. (DISCLAIMER : This needs to be tested for thread safety).

public class GlobalRequestMessageHandler : DelegatingHandler {
    internal static Lazy<HttpRequestMessage> CurrentRequest { get; private set; }
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        //Set global request for access in accessor instance
        CurrentRequest = new Lazy<HttpRequestMessage>(() => request, true);
        //continue down pipline
        var response = await base.SendAsync(request, cancellationToken);
        //reset request on way out
        CurrentRequest = null;
        return response;
    }
}

This would be added to the message handler early in the configuration

var messageHandler = new GlobalRequestMessageHandler();
config.MessageHandlers.Add(messageHandler);

The accessor proposed previously would be updated to use this new source for the current request and provide it to callers.

public class HttpRequestMessageAccessor : IHttpRequestMessageAccessor {
    public HttpRequestMessage Request {
        get {
            HttpRequestMessage request = null;
            if (GlobalRequestMessageHandler.CurrentRequest != null) {
                request = GlobalRequestMessageHandler.CurrentRequest.Value;
            }
            return request;
        }
    }
}

This feels like a hack and should be tested. My initial in-memory tests work but I am uncertain how it works in production.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • this will create a project circular reference, the controller project is depending on the service, because it calls service methods, but then the service project has to depend on the controller project to get the header info – Alex Gordon Jul 27 '17 at 18:26
  • No the service class does not have to depence on the controller. That is the main reason for having the abstraction in the first place – Nkosi Jul 27 '17 at 18:27
  • container.RegisterType(); container.RegisterType(new HierarchicalLifetimeManager()); – Alex Gordon Jul 27 '17 at 18:28
  • you're right, perhaps im incorrectly registering the types in unity, not sure – Alex Gordon Jul 27 '17 at 18:29
  • see anything wrong with that construction? opportunityservice is defined as public class OpportunityService.... public OpportunityService (IHttpRequestMessageAccessor accessor) – Alex Gordon Jul 27 '17 at 18:46
  • at first glance I see no issue with it. Where did you create the accessor. Which project is it defined in. – Nkosi Jul 27 '17 at 18:49
  • interface is defined in service layer, and the implementation is in the controller layer – Alex Gordon Jul 27 '17 at 18:51
  • Perfect. was just checking. – Nkosi Jul 27 '17 at 18:52
  • would i inject it into BOTH the controller and the service layer? – Alex Gordon Jul 27 '17 at 19:38
  • Unless you need to access it directly in the controller then there is no need to inject it into the controller if only the service needs it. The controller would only need the service and the service will need the accessor. – Nkosi Jul 27 '17 at 19:41
  • in your answer you linked to this example: https://stackoverflow.com/q/16670329/5233410 and per somebody's comment: "Note also that this is unlikely to work in a self-hosted environment (e.g. if you are using OWIN or similar), as HttpContext.Current won't be set." , and this is what is happening in my case – Alex Gordon Jul 27 '17 at 19:45
  • what do you say, sir? – Alex Gordon Jul 28 '17 at 04:53
  • thank you. in the meantime i've posted another question for you :) – Alex Gordon Jul 28 '17 at 15:54