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

时间:2013-09-23 14:55:42

标签: c# json rest servicestack

<TL;DR>

至少,我正在寻找一种方法来有条件地排除资源上的某些属性在每次调用的基础上包含在响应中(请参阅下面的fields)。

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

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

</TD;DR>

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

我将在这里描述这些功能,但我认为链接上的讨论可以更好地解释它们。

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

href - (视频中的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 - (视频中55:44)查询字符串参数,指示服务器仅在REST响应中包含所需资源的指定属性。

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

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

expand - (视频中的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/..."
            },
            ...
        ]
    }
}

2 个答案:

答案 0 :(得分:6)

所以......在我看来,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<BaseResponse> 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<BaseResponse>
                    {
                        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<Profile>(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<SocialNetwork>(testSocialNetwork, this.Request.QueryString);

        return testSocialNetwork;
    }
}

反思助手类

public static class ServiceHelper
{
    public static object BuildResponseObject<T>(T typedObject, NameValueCollection queryString) where T: BaseResponse
    {
        var newObject = new ExpandoObject() as IDictionary<string, object>;
        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;
    }
}

答案 1 :(得分:0)

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

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

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

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