【问题标题】:How to avoid repeating a parameter in every controller?如何避免在每个控制器中重复参数?
【发布时间】:2017-07-27 01:35:04
【问题描述】:

我想知道是否有办法避免重复自己将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 传递给每个服务方法?

附带说明一下,函数式方法的灵感主要来自 Vladimir KhorikovRalf Westphal

【问题讨论】:

  • 这可以通过依赖注入来完成,方法是在容器中与您拉取请求标头值相同的位置注册您的服务。非常类似于stackoverflow.com/questions/39459705/…
  • 这可能会转向意见领域,但如果您的服务类需要访问标头,那么控制器应该每次都传递它们.我不认为这是无用的重复,而是对服务类的依赖关系非常明确。
  • 服务应该不知道标头

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


【解决方案1】:

一种可能性是创建一个服务来提取当前的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;
        }
    }
}

这感觉像是 hack,应该进行测试。我最初的内存测试有效,但我不确定它在生产中的工作方式。

【讨论】:

  • 这将创建一个项目循环引用,控制器项目依赖于服务,因为它调用服务方法,但是服务项目必须依赖于控制器项目才能获取头信息跨度>
  • 不,服务类不必依赖于控制器。这是首先进行抽象的主要原因
  • container.RegisterType(); container.RegisterType(new HierarchicalLifetimeManager());
  • 你是对的,也许我错误地注册了统一的类型,不确定
  • 看到这个结构有什么问题吗? opportunityService 被定义为公共类 OpportunityService.... public OpportunityService (IHttpRequestMessageAccessor accessor)
猜你喜欢
  • 1970-01-01
  • 2017-06-16
  • 2013-08-09
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
  • 2015-06-04
  • 2016-05-06
  • 1970-01-01
相关资源
最近更新 更多