Skip to main content
 首页 » 编程设计

c#之WebAPI HttpContent 转换为类型化对象

2024年02月25日32cyq1162

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

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

编辑

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

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

请您参考如下方法:

All my APIs wrapped into 'Response' object.

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

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