ASP.NET WebApi和部分响应

5
我有一个正在开发的ASP.NET WebApi项目,老板希望返回结果支持“局部响应”,这意味着虽然数据模型可能包含50个字段,但客户端应该能够请求特定字段的响应。原因是如果他们正在实现例如列表,他们只需要50个字段中的姓、名和ID,而不需要所有50个字段的额外负担。目前,我已经通过使用自定义合同解析器(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可能会遇到线程问题。如果我更改它只针对一个请求,那么在此之后的所有请求中它都有效(通过测试似乎是这样)。如果是这种情况,那么我就不认为它对我的目的有用。
总之,我正在寻找一种动态数据模型的方法,以便客户端可以按照请求的方式配置请求的输出。Google在其Web API中实现了这一点,并称其为“部分响应”,效果很好。我的实现在某种程度上可以工作,但我担心它在多个同时请求时会出现问题。
有什么建议?提示?

只是提供信息...请查看下一个版本中即将推出的Json格式化程序的$select功能支持:https://aspnetwebstack.codeplex.com/wikipage?title=%24select%20and%20%24expand%20support&referringTitle=Specs - Kiran
我觉得我没有时间等下一个版本。我们还有不到两个月就要进行演示,我还有很多工作要做。更重要的是,我的方法是否从根本上存在问题。我基于网络上的建议和教程进行了实现,但是我对其基础有一些疑问,我并没有完全理解。 - Brian Mueller
如果您在每个请求上创建DynamicContractResolver的新实例并将其用作ContractResolver,那么就不应该出现并发问题。 - muratgu
muratgu,我认为你回答了我的问题,谢谢!Kiran - 感谢你提供的信息,我已经整理好备用了。我会再等一天看看是否还有其他建议或评论,然后我会关闭它。 - Brian Mueller
3个回答

6

一个可能有效的简单解决方案。

创建一个包含所有50个成员的模型类,这些成员都是可空类型。为请求的成员分配值。然后以正常方式返回结果。

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

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

现在这听起来像是一个有趣的替代方案,我之前没有考虑过。我会马上研究一下,因为我同意它可以简化一些流程,并将逻辑移动到API的数据转换层中,在那里我也可以拥有更多的控制权。 - Brian Mueller
我已经实现了这个解决方案,并且废弃了我的DynamicContractResolver。这似乎非常有效,并且有额外的好处,如更易读,更易理解以及提供了一些额外的灵活性,例如“必填返回字段”等。感谢您的帮助。 - Brian Mueller
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; 配置.格式化程序. Json格式化程序.序列化器设置.空值处理 = 空值处理.忽略; - codebased

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,因为在返回路径上被绕过并使用xml解析器。我也不想将自己锁定在json中,项目要求最终用户能够根据请求获取json或xml。如果他们选择xml,则老板不关心部分内容,因此我目前不关心该过程的这一方面。 - Brian Mueller

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接