Aspnet Core选择正文中复杂参数的动作

时间:2017-09-27 13:59:22

标签: c# asp.net-core asp.net-core-webapi

我在Aspnet Core 1.1中编写了一个API,但其中一个要求是每个请求必须是对单个路由的post请求,并且根据body负载中的类型加载一个动作,我尝试继承ActionMethodSelectorAttribute和实现IsValidForRequest然后装饰传递预期类型的​​每个Action这是一个简单的方法,它可以工作,但当我尝试使用RouteContext.HttpContext.Request.Body它是一个Stream对象,并且如果我尝试反序列化时问题就出现了它不止一次抛出异常并且我也使用了缓存,这有助于我避免异常,但是一旦选择了Action,主体已经消耗,并且不能用于再次序列化它并用于模型粘合剂。

public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{

    try
    {
        var body = new MemoryStream();
        routeContext.HttpContext.Request.Body.CopyTo(body);

        body.Position = 0;

        XmlDocument xmlDoc = new XmlDocument();
        var xml = XDocument.Load(body);
        var messageName = xml.Root.Name.LocalName;
        return messageName == _messageType.Name;

    }
    catch (Exception ex)
    {
        return false;
    }

}

[MessagBasedControllerActionSelector(typeof(OTA_HotelInvCountRQ))]
public async Task<IActionResult> OTA_HotelInvCount([FromBody]OTA_HotelInvCountRQ request)
{
    var response = await _otaService.OTA_HotelInvCountRQ(request, GetExternalProviderId());
    return Ok(response);
}

我知道这种方法无法扩展,我很乐意听取另一种解决方案或满足我要求的解决方案。

1 个答案:

答案 0 :(得分:0)

最后,我得到了这个工作,关键点是对routeContext.HttpContext.Request.EnableRewind()的方法调用,它允许通过放置其position = 0

重用体流

我使用内存缓存来减轻开销。

这是我的解决方案

 public class MessagBasedControllerActionSelectorAttribute : ActionMethodSelectorAttribute
    {
        private const string RequestHashCodeKey = "RequestHashCodeKey";
        private const string MessageRootNameKey = "MessageRootNameKey";
        private readonly string _messageTypeName;
        private ICacheService _cache;

        public MessagBasedControllerActionSelectorAttribute(string messageTypeName)
        {
            _messageTypeName = messageTypeName;
        }

        public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
        {
            //Get reference to cache from DI container
            _cache = routeContext.HttpContext.RequestServices.GetService(typeof(ICacheService)) as ICacheService;

            //Get Request hashCode from cache if is possible
            var existingRequestHash = _cache.Get<string>(RequestHashCodeKey);
            var req = routeContext.HttpContext.Request;
            var messageName = string.Empty;
            int.TryParse(existingRequestHash, out int cacheRequestHash);

            //Verify if the incoming request is the same that has been cached before or deserialize the body in case new request
            if (cacheRequestHash != req.GetHashCode())
            {
                //store new request hash code
                _cache.Store(RequestHashCodeKey, req.GetHashCode().ToString());
                //enable to rewind the body stream this allows then put position 0 and let the model binder serialize again
                req.EnableRewind();
                var xmlBody = "";
                req.Body.Position = 0;

                //Read XML
                using (StreamReader reader
                    = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
                {
                    xmlBody = reader.ReadToEnd();
                }


                XmlDocument xmlDoc = new XmlDocument();
                var xml = XDocument.Parse(xmlBody);
                //Get the root name
                messageName = xml.Root.Name.LocalName;
                //Store the root name in cache
                _cache.Store(MessageRootNameKey, messageName);

                req.Body.Position = 0;
            }
            else
            {
                //Get root name from cache
                messageName = _cache.Get<string>(MessageRootNameKey);
            }

            return messageName == _messageTypeName;
        }
    }

在控制器中使用它:

 [MessagBasedControllerActionSelector(nameof(My_Request_Type))]
        public async Task<IActionResult> MyAction([FromBody]My_Request_Typerequest)
        {
            //Some cool stuff
            return Ok(response);
        }