ServiceStack支持在每次调用的基础上有条件地忽略REST响应中的字段

至少,我正在寻找一种方法来有条件地排除资源上的某些属性,使其不被包含在每个呼叫的响应中(参见下面的fields )。

理想情况下,我想使用ServiceStack实现REST服务,该服务支持以下所有要点。

UPDATE
虽然我一般都非常喜欢ServiceStack的方法,但如果可能的话,我更愿意使用它,如果它不是特别适合这些想法,我宁可不要向后弯腰,让它变得有效。 如果是这样的话,任何人都可以指向另一个可能更合适的c#框架吗? 当然,我正在积极探索其他选择。

在本次题为“ 设计REST + JSON API ”的演讲中,演示者在JSON中描述了他的资源引用策略(通过资源上的href属性)。 除此之外,他还描述了两个查询参数( fieldsexpand ),用于控制调用REST服务的响应所包含的数据。 我一直在努力尝试挖掘ServiceStack框架以获得对fields支持,但迄今为止一直没有成功。 这在ServiceStack目前是否可行? 理想情况下,解决方案将与格式无关,因此可以使用所有ServiceStack支持的输出格式。 我认为expand将采用相同的策略。

我将在这里描述这些function,但我认为链接上的谈话可以更好地解释它们。

假设我们有一个Profiles资源,其中包含以下属性: givenNamesurnamegenderfavColor 。 “个人档案”资源还包括用户在socialNetworks属性中所属的社交网络列表。

href – (video中的42:22)每个资源都包含REST服务上的完整链接。 将返回对GET /profiles/123调用

 { "href":"https://host/profiles/123", "givenName":"Bob", "surname":"Smith", "gender":"male", "favColor":"red", "socialNetworks": { "href":"https://host/socialNetworkMemberships?profileId=123" } } 

请注意, socialNetworks属性返回的对象仅填充了href值。 这样可以使响应更短且更集中,同时还为最终用户提供足够的信息,以便在需要时提供进一步的请求 在这个庄园中全面使用的href属性使得重用资源数据结构作为其他资源的子项变得容易(概念上无论如何)。

fields – (video中55:44)查询字符串参数,指示服务器仅在REST响应中包含所需资源的指定属性。

GET /profiles/123正常响应将包括资源的所有属性,如上所示。 当fields查询参数包含在请求中时,仅返回指定的字段。 ‘GET / propfiles / 123?fields = surname,favColor’会返回

 { "href":"https://host/profiles/123", "surname":"Smith", "favColor":"red" } 

expand – (video中的45:53)查询字符串参数,指示服务器充实结果中的指定子资源。 使用我们的示例,如果您要调用GET /profiles/123?expand=socialNetworks您可能会收到类似的内容

 { "href":"https://host/profiles/123", "givenName":"Bob", "surname":"Smith", "gender":"male", "favColor":"red", "socialNetworks": { "href":"https://host/socialNetworkMemberships?profileId=123", "items": [ { "href":"https://host/socialNetworkMemberships/abcde", "siteName":"Facebook", "profileUrl":"http://www.facebook.com/..." }, ... ] } } 

所以…在我看来,ServiceStack的最大特点是它使得通过HTTP发送,接收和处理POCO非常容易。 您如何设置POCO以及您之间(在“服务”中)之间的操作取决于您。 SS有意见吗? 是。 你必须同意他们吗? 不。(但你可能应该:))

我认为扩展下面这样的东西会让你接近你想要处理api的方式。 可能不是ServiceStack的最佳示例,但ServiceStack代码/要求几乎不会引起注意,并且不会妨碍您(AppHost配置未显示)。 您可以在其他.NET框架(MVC / Web API /等)中执行类似的操作,但在我看来,它看起来不像使用ServiceStack那样直接的C#/ .NET代码。

请求课程

 [Route("/Profiles/{Id}")] public class Profiles { public int? Id { get; set; } } [Route("/SocialNetworks/{Id}")] public class SocialNetworks { public int? Id { get; set; } } 

基本响应类

 public class BaseResponse { protected virtual string hrefPath { get { return ""; } } public string Id { get; set; } public string href { get { return hrefPath + Id; } } } 

来自示例的类

 public class Profile : BaseResponse { protected override string hrefPath { get { return "https://host/profiles/"; } } public string GivenName { get; set; } public string SurName { get; set; } public string Gender { get; set; } public string FavColor { get; set; } public List SocialNetworks { get; set; } } public class SocialNetwork: BaseResponse { protected override string hrefPath { get { return "https://host/socialNetworkMemberships?profileId="; }} public string SiteName { get; set; } public string ProfileUrl { get; set; } } 

服务

 public class ProfileService : Service { public object Get(Profiles request) { var testProfile = new Profile { Id= "123", GivenName = "Bob", SurName = "Smith", Gender = "Male", FavColor = "Red", SocialNetworks = new List { new SocialNetwork { Id = "abcde", SiteName = "Facebook", ProfileUrl = "http://www.facebook.com/"} } }; if (!String.IsNullOrEmpty(this.Request.QueryString.Get("fields")) || !String.IsNullOrEmpty(this.Request.QueryString.Get("expand"))) return ServiceHelper.BuildResponseObject(testProfile, this.Request.QueryString); return testProfile; } } public class SocialNetworkService : Service { public object Get(SocialNetworks request) { var testSocialNetwork = new SocialNetwork { Id = "abcde", SiteName = "Facebook", ProfileUrl = "http://www.facebook.com/" }; if (!String.IsNullOrEmpty(this.Request.QueryString.Get("fields")) || !String.IsNullOrEmpty(this.Request.QueryString.Get("expand"))) return ServiceHelper.BuildResponseObject(testSocialNetwork, this.Request.QueryString); return testSocialNetwork; } } 

反思助手class

 public static class ServiceHelper { public static object BuildResponseObject(T typedObject, NameValueCollection queryString) where T: BaseResponse { var newObject = new ExpandoObject() as IDictionary; newObject.Add("href", typedObject.href); if (!String.IsNullOrEmpty(queryString.Get("fields"))) { foreach (var propertyName in queryString.Get("fields").Split(',').ToList()) { //could check for 'socialNetwork' and exclude if you wanted newObject.Add(propertyName, typedObject.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(typedObject, null)); } } if (!String.IsNullOrEmpty(queryString.Get("expand"))) { foreach (var propertyName in queryString.Get("expand").Split(',').ToList()) { newObject.Add(propertyName, typedObject.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(typedObject, null)); } } return newObject; } } 

通常,您可以通过设置DataMember属性来控制DTO的序列化。 使用这些属性,您可以控制属性是否应具有默认值。 这意味着如果您只是不定义要返回的对象的属性,则不应该序列化它,因此不会在生成的Json中显示。

ServiceStack内部使用标准DataContract … Serializer,因此应该支持此function

否则,您也可以使用动态对象,只需在运行时编写对象,对其进行序列化并将其发回。 这是一个非常基本的例子:

  var seri = JsonSerializer.Create(new JsonSerializerSettings() { }); using (var textWriter = new StringWriter()) { var writer = new JsonTextWriter(textWriter); dynamic item = new { Id = id }; seri.Serialize(writer, item); return textWriter.ToString(); }