如何避免在每个控制器中重复参数?

时间:2017-07-27 01:35:04

标签: c# asp.net .net asp.net-web-api visual-studio-2017

我想知道是否有办法避免重复自己将Request.Headers传递给每个服务方法?

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

客户端代码如下:

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

在我尝试重构时,一个非常好的建议是合并服务层和控制器层:

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

然而,这并没有解决我对违反DRY的担忧。

然后我尝试了一种更具功能性的方法,最终可能会成功,但它可能需要更强大。它需要处理不同的HTTP动词。如您所见,这些功能都是静态的。没有依赖关系,几乎没有状态突变:

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

虽然不一定是问题的核心,但这就是我定义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方法时都必须指定?

作为旁注,功能方法主要受到Vladimir Khorikov以及Ralf Westphal的启发。

1 个答案:

答案 0 :(得分:2)

一种可能性是创建一个服务来提取当前Request

如果在IIS中托管它,则ASP管道将Web API消息对象存储在当前HttpContext上。

一旦存在,您可以通过

访问它
HttpContext.Current.Items["MS_HttpRequestMessage"]

来源:How to access the current HttpRequestMessage object globally?

有了这个服务就可以了。让我们说像

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的构造函数中不可用,因为尚未创建请求。

更新:自托管

如果没有在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;
        }
    }
}

这感觉就像一个黑客,应该进行测试。我最初的内存测试有效,但我不确定它在生产中是如何工作的。