简体   繁体   English

WebAPI HttpContent转换为类型化对象

[英]WebAPI HttpContent convert to typed object

I am working on custom filter which should accomplish simple thing. 我正在研究自定义过滤器,它应该完成简单的事情。 All my APIs wrapped into 'Response' object. 我的所有API都包含在“Response”对象中。 I want to fill in all properties using filter. 我想使用过滤器填写所有属性。 This is code I have for the filter: 这是我对过滤器的代码:

public class MeteringFilter : IActionFilter
    {
        public Task<HttpResponseMessage> ExecuteActionFilterAsync(
            HttpActionContext actionContext,
            CancellationToken cancellationToken,
            Func<Task<HttpResponseMessage>> continuation)
        {
            var attribute =
                actionContext.ActionDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault() ??
                actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault();

            if (attribute == null) return continuation();

            var operation = actionContext.ActionDescriptor.ActionName;
            var user = actionContext.RequestContext.Principal.Identity.Name;
            var started = DateTimeOffset.Now;
            return continuation().ContinueWith(t => 
            {
                var completed = DateTimeOffset.Now;
                var duration = completed - started;

                var c = t.Result.Content;
                // This is code which does not work but I like to have:
                // When debugger stops here I can see Content.Value and my object but I can't use this property like below
                var cv = t.Result.Content.Value as Response<object>;

                return t.Result;
            });
        }

        public bool AllowMultiple => true;
    }

I found similar question where it was suggested to do var c = t.Result.Content.ReadAsAsync(typeof(Response<>)); 我发现类似的问题,建议做var c = t.Result.Content.ReadAsAsync(typeof(Response<>)); but I can't do this because I can't make lambda function async in this case. 但是我不能这样做因为在这种情况下我不能使lambda函数async

Any suggestion on how to get typed object out of HttpContent so I can assign properties before it returns to caller? 关于如何从HttpContent中获取类型对象的任何建议,以便我可以在返回调用者之前分配属性?

Here is Response<T> 这是Response<T>

public class Response<T>
    {
        public string Url { get; set; }

        public DateTime ServerTime { get; set; }

        public TimeSpan TimeTook { get; set; }

        public T Data { get; set; }

        public Error Error { get; set; }
    }

EDIT 编辑

Here is how code looks now. 这是代码现在的样子。 I do get access to object, but webservice does not respond with data I fill to client. 我确实可以访问对象,但是webservice不响应我填写给客户端的数据。 It seems that code executed later after serialization/media formatting takes place. 似乎在序列化/媒体格式化之后执行代码。

I guess question becomes how do I add generic "handler" before web service returns but with access to beginning of call (so I can measure times, see request params, etc) 我想问题是如何在Web服务返回之前添加通用的“处理程序”,但是可以访问调用的开始(所以我可以测量时间,请参阅请求参数等)

return continuation().ContinueWith(t => 
            {
                var c = t.Result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
                if (c.Result is Response<object> response)
                {
                    Debug.WriteLine("Adding times");
                    response.ServerTime = startedOn;
                    response.TimeTook = DateTime.Now - startedOn;
                }

                return t.Result;
            }, cancellationToken);

EDIT 2: 编辑2:

Here is sample web api method which I want to intercept: 这是我要拦截的示例web api方法:

[HttpGet]
        public Response<LookupResponseData> Carrier(int? key = null, string id = "")
        {
            return this.GetKeyIdBundleForLookup("Carriers", key, id);
        }


private Response<LookupResponseData> GetKeyIdBundleForLookup(string lookupId, int? key, string id)
        {
            if (!key.HasValue && string.IsNullOrEmpty(id))
                return new Response<LookupResponseData>
                {
                    Error = new Error { Code = ErrorCodes.InvalidQueryParameter, Message = "Either key or id must be specified" }
                };

            var r = new Response<LookupResponseData>();
            try
            {
                this.LookupService.GetKeyIdDescription(this.AccountId, lookupId, key, id, out var keyResult, out var idResult, out var description);
                if (!keyResult.HasValue)
                    return new Response<LookupResponseData>
                    {
                        Error = new Error { Code = ErrorCodes.InvalidOrMissingRecord, Message = "No record found for parameters specified" }
                    };

                r.Data = new LookupResponseData { Key = keyResult.Value, Id = idResult, Description = description };
            }
            catch (Exception ex)
            {
                this.LoggerService.Log(this.AccountId, ex);
                return new Response<LookupResponseData>
                {
                    Error = new Error { Code = ErrorCodes.Unknown, Message = "API Call failed, please contact support. Details logged." }
                };
            }

            return r;
        }

All my APIs wrapped into 'Response' object. 我的所有API都包含在“Response”对象中。

First you can simplify your results by creating a implicit operators: 首先,您可以通过创建隐式运算符来简化结果:

public class Response
{
    public string Url { get; set; }
    public DateTime ServerTime { get; set; }
    public TimeSpan TimeTook { get; set; }
}

public class Response<T> : Response
{
    public T Data { get; set; }
    public Error Error { get; set; }

    public static implicit operator Response<TData>(TData data)
    {
        var result = new Response<TData>
        {
          Data = data,
        };

        return result;
    }

    public static implicit operator Response<TData>(Error error)
    {
        var result = new Response<TData>
        {
          Error = error,
        };

        return result;
    }
}

Now it should be easier to really ignore the repeated code of creating the response: 现在应该更容易真正忽略创建响应的重复代码:

private Response<LookupResponseData> GetKeyIdBundleForLookup(
  string lookupId, int? key, string id)
{
  if (!key.HasValue && string.IsNullOrEmpty(id))
    return new Error 
    { 
      Code = ErrorCodes.InvalidQueryParameter, 
      Message = "Either key or id must be specified" 
    };

  try
  {
    this.LookupService.GetKeyIdDescription(this.AccountId, 
      lookupId, 
      key, 
      id, 
      out var keyResult, 
      out var idResult, 
      out var description);
    if (!keyResult.HasValue)
      return new Error 
      {
        Code = ErrorCodes.InvalidOrMissingRecord, 
        Message = "No record found for parameters specified" 
      };

    return new LookupResponseData 
    { 
      Key = keyResult.Value, 
      Id = idResult, Description = description 
    };

  catch (Exception ex)
  {
    this.LoggerService.Log(this.AccountId, ex);
    return new Error 
    { 
      Code = ErrorCodes.Unknown, 
      Message = "API Call failed, please contact support. Details logged." }
    };
  }
}

Then you can create an Core Async Action Filter : 然后,您可以创建核心异步操作筛选器

public class SampleAsyncActionFilter : IAsyncActionFilter
{
  public async Task OnActionExecutionAsync(
    ActionExecutingContext context,
    ActionExecutionDelegate next)
  {
    // do something before the action executes
    var started = DateTimeOffset.Now;     

    // Action Executes
    var resultContext = await next();

    // do something after the action executes; resultContext.Result will be set
    if  (result.Context.Result is Response response)
    {
      response.ServerTime = started;
      response.TimeTook = DateTimeOffset.Now - started;
    }
  }
}

Or Non-Core (MVC): 或非核心(MVC):

public class SampleActionFilter : ActionFilterAttribute
{
  private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";

  public override void OnActionExecuting(ActionExecutingContext context)
  {
    context.HttpContext.Items[TimerKey] = DateTimeOffset.Now;
  }

  public override void OnActionExecuted(ActionExecutedContext context)
  {
    if (context.Result is Response response)
      && context.HttpContext.Items[TimerKey] is DateTimeOffset started) 
    {
      response.ServerTime = started;
      response.TimeTook = DateTimeOffset.Now - started;
    }
  }

Or Non-Core (WebApi): 或非核心(WebApi):

public class SampleActionFilter : ActionFilterAttribute
{
  private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";

  public override void OnActionExecuting(HttpActionContext context)
  {
    context.Request.Properties[TimerKey] = DateTimeOffset.Now;
  }

  public override void OnActionExecuted(HttpActionExecutedContext context)
  {
    if (context.Result is Response response)
      && context.Request.Properties[TimerKey] is DateTimeOffset started) 
    {
      response.ServerTime = started;
      response.TimeTook = DateTimeOffset.Now - started;
    }
  }

I tweaked your code. 我调整了你的代码。 I hope it helps. 我希望它有所帮助。 I couldn't check syntax errors though 我无法检查语法错误

return await continuation().ContinueWith(async t => 
{ 
    var result = await t;
    var c = await result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
    if (c is Response<object> response)
    {
        Debug.WriteLine("Adding times");
        response.ServerTime = startedOn;
        response.TimeTook = DateTime.Now - startedOn;
    }

    return result;
}, cancellationToken);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM