我正在开发一个ASP.NET WebApi项目。老板希望返回支持“部分响应”,这意味着尽管数据模型可能包含50个字段,但客户端应该能够为响应请求特定字段。原因是如果他们实现例如列表他们根本不需要所有50个字段的开销,他们可能只想要名字,姓氏和Id来生成列表。到目前为止,我已经使用自定义合约解析器(DynamicContractResolver)实现了一个解决方案,这样当请求进入时,我通过OnActionExecuting方法中的过滤器(FieldListFilter)窥视它,并确定是否存在名为“FieldList”的字段,然后,如果它是我用我的DynamicContractResolver的新实例替换当前的ContractResolver,我将fieldlist传递给构造函数。
一些示例代码
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在他们的网络API中实现了这一点,他们称之为“部分响应”并且效果很好。我的实现工作到了一定程度,但我担心它会因多个同时请求而中断。
连连呢?提示?
答案 0 :(得分:5)
可能有效的简单解决方案。
创建一个模型类,其中包含所有50个具有可空类型的成员。 将值分配给请求的成员。 只需以正常方式返回结果。
在WebApiConfig.Register()中,您必须设置空值处理。
config.Formatters.JsonFormatter.SerializerSettings =
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
答案 1 :(得分: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。因此,您配置到配置中的任何内容(例如媒体类型映射)都不会通过此方法可用。
答案 2 :(得分:0)
我知道这个问题来自很多年前,但如果您希望使用现代版本的框架来做这件事,我现在建议使用OData服务(http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value)。