How to avoid repeating a parameter in each controller?

I am wondering if there is a way to avoid repetition in passing Request.Headersto each 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 refactoring attempts, a very good suggestion was to combine service levels and a controller:

[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, this does not solve my DRY violation problem.

Then I tried a more functional approach that might ultimately succeed, but perhaps it should be more reliable. He will need to handle different HTTP verbs. As you can see, all functions are static. There are no dependencies and almost no state mutations:

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;
        }

Although this is not necessarily central to the issue, I have 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();
    }
}

, Request.Headers serivce?

, .

+4
1

Request.

IIS, ASP -API HttpContext.

,

HttpContext.Current.Items["MS_HttpRequestMessage"]

: HttpRequestMessage ?

. -

public interface IHttpRequestMessageAccessor {
    HttpRequestMessage Request { get; }
}

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;
        }
    }
}

. , DI.

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;
    }
}

, - , , . ApiController, .

: Self Hosting

IIS, HttpContext .

, , , , . ( : ).

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;
    }
}

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

, , , .

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

. , , .

+2

Source: https://habr.com/ru/post/1682357/


All Articles