WebAPI HttpContent转换为键入的对象

时间:2018-12-24 19:04:03

标签: c# asp.net-web-api

我正在研究应该完成简单事情的自定义过滤器。我所有的API都包装到“ Response”对象中。我想使用过滤器填写所有属性。这是我用于过滤器的代码:

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

我在建议执行var c = t.Result.Content.ReadAsAsync(typeof(Response<>));的地方发现了类似的问题,但是我不能这样做,因为在这种情况下我无法使lambda函数async

关于如何从HttpContent中获取类型化对象的任何建议,这样我就可以在返回给调用者之前分配属性?

这里是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; }
    }

编辑

这是现在的代码外观。我确实可以访问对象,但是Webservice不会响应我填充给客户端的数据。似乎是在序列化/媒体格式化之后执行的代码。

我想问题变成了如何在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);

编辑2:

这是我想拦截的示例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;
        }

2 个答案:

答案 0 :(得分:3)

  

我所有的API都包裹在“ Response”对象中。

首先,您可以通过创建隐式运算符来简化结果:

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

现在,真正忽略创建响应的重复代码应该更容易:

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

然后您可以创建一个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;
    }
  }
}

或非核心(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;
    }
  }

或非核心(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;
    }
  }

答案 1 :(得分:0)

我调整了您的代码。希望对您有所帮助。 我虽然无法检查语法错误

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