【问题标题】:ASP.NET WebApi and Partial ResponsesASP.NET WebApi 和部分响应
【发布时间】:2013-06-06 16:02:09
【问题描述】:

我正在处理一个 ASP.NET WebApi 项目。老板希望返回支持“部分响应”,这意味着尽管数据模型可能包含 50 个字段,但客户端应该能够为响应请求特定字段。原因是,如果他们正在实现例如一个列表,他们根本不需要所有 50 个字段的开销,他们可能只需要 First Name、Last Name 和 Id 来生成列表。到目前为止,我已经通过使用自定义合同解析器 (DynamicContractResolver) 实现了一个解决方案,这样当请求进来时,我通过 OnActionExecuting 方法中的过滤器 (FieldListFilter) 窥视它并确定是否存在名为“FieldList”的字段并且那么如果是我用我的 DynamicContractResolver 的新实例替换当前的 ContractResolver 并将字段列表传递给构造函数。

一些示例代码

DynamicContractResolver.cs

protected override IList<JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
    {
        List<String> fieldList = ConvertFieldStringToList();

        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
        if (fieldList.Count == 0)
        {
            return properties;
        }
        // If we have fields, check that FieldList is one of them.
        if (!fieldList.Contains("FieldList"))
            // If not then add it, FieldList must ALWAYS be a part of any non null field list.
            fieldList.Add("FieldList");
        if (!fieldList.Contains("Data"))
            fieldList.Add("Data");
        if (!fieldList.Contains("FilterText"))
            fieldList.Add("FilterText");
        if (!fieldList.Contains("PageNumber"))
            fieldList.Add("PageNumber");
        if (!fieldList.Contains("RecordsReturned"))
            fieldList.Add("RecordsReturned");
        if (!fieldList.Contains("RecordsFound"))
            fieldList.Add("RecordsFound");
        for (int ctr = properties.Count-1; ctr >= 0; ctr--)
        {
            foreach (string field in fieldList)
            {
                if (field.Trim() == properties[ctr].PropertyName)
                {
                    goto Found;
                }
            }
            System.Diagnostics.Debug.WriteLine("Remove Property at Index " + ctr + " Named: " + properties[ctr].PropertyName);
            properties.RemoveAt(ctr);
        // Exit point for the inner foreach.  Nothing to do here.
        Found: { }
        }
        return properties;
    }

FieldListFilter.cs

public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
        // We need to determine if there is a FieldList property of the model that is being used.
        // First get a reference to the model.
        var modelObject = actionContext.ActionArguments.FirstOrDefault().Value;
        string fieldList = string.Empty;
        try
        {
            // Using reflection, attempt to get the value of the FieldList property
            var fieldListTemp = modelObject.GetType().GetProperty("FieldList").GetValue(modelObject);
            // If it is null then use an empty string
            if (fieldListTemp != null)
            {
                fieldList = fieldListTemp.ToString();
            }
        }
        catch (Exception)
        {
            fieldList = string.Empty;
        }

        // Update the global ContractResolver with the fieldList value but for efficiency only do it if they are not the same as the current ContractResolver.
        if (((DynamicContractResolver)GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver).FieldList != fieldList)
        {
            GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DynamicContractResolver(fieldList);
        }
    }

然后我可以发送一个带有 json 内容负载的请求,如下所示:

{
  "FieldList":"NameFirst,NameLast,Id",
  "Data":[
    {
      "Id":1234
    },
    {
      "Id":1235
    }
  ]
}

我会收到这样的回复:

{
  "FieldList":"NameFirst,NameLast,Id",
  "Data":[
    {
      "NameFirst":"Brian",
      "NameLast":"Mueller",
      "Id":1234
    },
    {
      "NameFirst":"Brian",
      "NameLast":"Mueller",
      "Id":1235
    }
  ]
}

我相信使用 ContractResolver 可能会遇到线程问题。如果我为一个请求更改它是否对此后的所有请求都有效,直到有人根据另一个请求更改它(通过测试似乎如此)如果是这样,那么我看不到对我的目的有用。

总而言之,我正在寻找一种拥有动态数据模型的方法,以便客户端可以根据请求配置请求的输出。谷歌在他们的 web api 中实现了这一点,他们称之为“部分响应”,效果很好。我的实现在某种程度上有效,但我担心它会因多个同时请求而中断。

建议?提示?

【问题讨论】:

  • 仅供参考...检查 $select 对 Json 格式化程序的功能支持,该功能将在下一版本中推出:aspnetwebstack.codeplex.com/…
  • 别以为我有时间等待下一个版本。我们在不到 2 个月的时间里就有了一个演示,我有很多东西要实现。更重要的是,是我的方法从根本上坏掉了。我是根据网络上的建议和教程实现的,但我只是对它的基础有一些我不完全理解的问题。
  • 如果您在每个请求上创建一个DynamicContractResolver 的新实例并将其用作ContractResolver,则不应该存在并发问题。
  • muratgu,我想你回答了我的问题,谢谢!和 Kiran - 感谢我将其归档以备后用的信息。我会给这个一天,看看有没有其他人有建议或 cmet,然后我会关闭它。

标签: json model-view-controller asp.net-web-api


【解决方案1】:

您不得触摸配置。您需要基于每个请求的合同解析器。您可以像这样在您的操作方法中使用它。

public class MyController : ApiController
{
    public HttpResponseMessage Get()
    {
        var formatter = new JsonMediaTypeFormatter();
        formatter.SerializerSettings.ContractResolver = 
              new DynamicContractResolver(new List<string>()
                       {"Id", "LastName"}); // you will get this from your filter

        var dto = new MyDto()
              { FirstName = "Captain", LastName = "Cool", Id = 8 };

        return new HttpResponseMessage()
        {
            Content = new ObjectContent<MyDto>(dto, formatter)
        };
        // What goes out is {"LastName":"Cool","Id":8}
    }
}

通过这样做,您将自己锁定为响应消息的 JSON 内容类型,但您已经通过使用 Json.NET 特定功能做出了该决定。另外,请注意您正在创建一个新的 JsonMediaTypeFormatter。因此,您在配置中配置的任何内容(例如媒体类型映射)都无法通过这种方法获得。

【讨论】:

  • Badri,通过触摸全局配置我将自己锁定在 Json 中是不正确的。我已经通过发送请求标头 accept: application/xml 对此进行了测试,并且它返回 xml 而不管 contractresolver 是什么,因为它在返回路径上被绕过并且它使用 xml 解析器。我也不想将自己锁定在 json 中,项目要求最终用户能够根据请求取回 json 或 xml。老板不关心部分内容,如果他们选择xml,所以我现在不关心过程的那一面。
【解决方案2】:

一个更简单的可行解决方案。

创建一个包含所有 50 个可空类型成员的模型类。 为请求的成员分配值。 只需以正常方式返回结果即可。

在您的 WebApiConfig.Register() 中,您必须设置空值处理。

   config.Formatters.JsonFormatter.SerializerSettings =
        new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };

【讨论】:

  • 现在这听起来像是一个有趣的替代方案,我没有考虑过。我将立即对此进行研究,因为我同意它会简化一些流程并将逻辑移动到 API 的数据转换层,在那里我也可以拥有更多控制权。
  • 我实现了这个解决方案并取消了我的 DynamicContractResolver。这似乎工作得很好,并且具有更易读、更易于理解的额外好处,并为我提供了一些额外的灵活性来拥有诸如“必需的返回字段”之类的东西。感谢您的帮助。
  • config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
【解决方案3】:

我知道这个问题来自多年前,但如果您希望使用现代版本的框架来解决此问题,我建议现在使用 OData 服务 (http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-18
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-08
    相关资源
    最近更新 更多