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

Cha*_*phs 5 c# rest json servicestack

<TL;DR>

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

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

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

</TD;DR>

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

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

比方说,我们有以下属性的档案资源:givenName,surname,gender,和favColor."个人档案"资源还包括用户在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"
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该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"
}
Run Code Online (Sandbox Code Playgroud)

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/..."
            },
            ...
        ]
    }
}
Run Code Online (Sandbox Code Playgroud)

paa*_*hpa 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; }
}
Run Code Online (Sandbox Code Playgroud)

基本响应类

public class BaseResponse
{
    protected virtual string hrefPath
    {
        get { return ""; }
    }

    public string Id { get; set; }
    public string href { get { return hrefPath + Id; } }
}
Run Code Online (Sandbox Code Playgroud)

来自示例的类

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; }
}
Run Code Online (Sandbox Code Playgroud)

服务

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

反思助手班

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;
    }
}
Run Code Online (Sandbox Code Playgroud)