ReactiveX 學習筆記(15)使用 Rx.NET + Json.NET 調用 REST API

JSON : Placeholder

JSON : Placeholder (https://jsonplaceholder.typicode.com/) 是一個用於測試的 REST API 網站。
如下使用 Task API/Rx.NET + Json.NET 調用該網站的 REST API,獲取字符串以及 JSON 數據。json

  • GET /posts/1
  • GET /posts
  • POST /posts
  • PUT /posts/1
  • DELETE /posts/1

全部 GET API 都返回JSON數據,格式(JSON-Schema)以下:api

{
  "type":"object",
  "properties": {
    "userId": {"type" : "integer"},
    "id": {"type" : "integer"},
    "title": {"type" : "string"},
    "body": {"type" : "string"}
  }
}

Post 對象

public class Post
{
    [JsonProperty("userId")]
    public int UserId { get; set; }
    [JsonProperty("id")]
    public int Id { get; set; }
    [JsonProperty("title")]
    public string Title { get; set; }
    [JsonProperty("body")]
    public string Body { get; set; }

    public override string ToString() =>
        $"Post {{userId = {UserId}, id = {Id}, title = \"{Title}\", body = \"{Body.Replace("\n", "\\n")}\"}}";
}

Post 對象負責 .NET 對象和 JSON 數據的相互轉換。
Post 類使用了 Json.NET 所提供的 attributes。app

Task API

public class PostDataStoreByTask
{
    protected HttpClient client = new HttpClient
    {
        BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
    };

    public async Task<string> GetPostAsString(int id) =>
    !CrossConnectivity.Current.IsConnected ? null : await client.GetStringAsync($"posts/{id}");

    public async Task<Post> GetPostAsJson(int id)
    {
        if (!CrossConnectivity.Current.IsConnected) return null;

        var json = await client.GetStringAsync($"posts/{id}");
        var item = await Task.Run(() => JsonConvert.DeserializeObject<Post>(json));

        return item;
    }

    public async Task<IEnumerable<Post>> GetPosts(int n)
    {
        IEnumerable<Post> items = new List<Post>();
        if (CrossConnectivity.Current.IsConnected)
        {
            var json = await client.GetStringAsync("posts");
            items = await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<Post>>(json));
        }
        return items.Take(n);
    }

    private StringContent GetStringContent(Post item) =>
    new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");

    public async Task<Post> CreatePost(Post item)
    {
        if (item == null || !CrossConnectivity.Current.IsConnected) return null;

        var response = await client.PostAsync("posts", GetStringContent(item));
        if (!response.IsSuccessStatusCode) return null;

        var json = await response.Content.ReadAsStringAsync();
        var item2 = await Task.Run(() => JsonConvert.DeserializeObject<Post>(json));

        return item2;
    }

    public async Task<Post> UpdatePost(Post item)
    {
        if (item == null || !CrossConnectivity.Current.IsConnected) return null;

        var response = await client.PutAsync($"posts/{item.Id}", GetStringContent(item));
        if (!response.IsSuccessStatusCode) return null;

        var json = await response.Content.ReadAsStringAsync();
        var item2 = await Task.Run(() => JsonConvert.DeserializeObject<Post>(json));

        return item2;
    }

    public async Task<string> DeletePost(int id)
    {
        if (!CrossConnectivity.Current.IsConnected) return null;

        var response = await client.DeleteAsync($"posts/{id}");
        if (!response.IsSuccessStatusCode) return null;

        var json = await response.Content.ReadAsStringAsync();
        return json;
    }
}
  • getPostAsString 方法取出第1個Post,返回字符串
  • getPostAsJson 方法取出第1個Post,返回Post對象
  • getPosts 方法取出前n個Post,返回n個Post對象
  • createPost 方法建立1個Post,返回所建立的Post對象
  • updatePost 方法更新第1個Post,返回所更新的Post對象
  • deletePost 方法刪除第1個Post,返回字符串

Rx.NET

public class PostDataStoreByRx
{
    protected HttpClient client = new HttpClient
    {
        BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
    };

    public IObservable<string> GetPostAsString(int id) =>
    !CrossConnectivity.Current.IsConnected ? Observable.Empty<string>() :
        client.GetStringAsync($"posts/{id}").ToObservable();

    public IObservable<Post> GetPostAsJson(int id) =>
    !CrossConnectivity.Current.IsConnected? Observable.Empty<Post>() :
        client.GetStringAsync($"posts/{id}").ToObservable()
        .Select(json => JsonConvert.DeserializeObject<Post>(json));

    public IObservable<Post> GetPosts(int n) =>
    !CrossConnectivity.Current.IsConnected ? Observable.Empty<Post>() :
        client.GetStringAsync("posts").ToObservable()
        .SelectMany(json => JsonConvert.DeserializeObject<IEnumerable<Post>>(json))
        .Take(n);

    private StringContent GetStringContent(Post item) =>
    new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");

    public IObservable<Post> CreatePost(Post item) =>
    item == null || !CrossConnectivity.Current.IsConnected ? Observable.Empty<Post>() :
        client.PostAsync("posts", GetStringContent(item)).ToObservable()
        .SelectMany(response => !response.IsSuccessStatusCode ? Observable.Empty<Post>() :
            response.Content.ReadAsStringAsync().ToObservable()
            .Select(json => JsonConvert.DeserializeObject<Post>(json)));

    public IObservable<Post> UpdatePost(Post item) =>
    item == null || !CrossConnectivity.Current.IsConnected? Observable.Empty<Post>() :
        client.PutAsync($"posts/{item.Id}", GetStringContent(item)).ToObservable()
        .SelectMany(response => !response.IsSuccessStatusCode? Observable.Empty<Post>() :
            response.Content.ReadAsStringAsync().ToObservable()
            .Select(json => JsonConvert.DeserializeObject<Post>(json)));

    public IObservable<string> DeletePost(int id) =>
    !CrossConnectivity.Current.IsConnected ? Observable.Empty<string>() :
        client.DeleteAsync($"posts/{id}").ToObservable()
        .SelectMany(response => !response.IsSuccessStatusCode ? Observable.Empty<string>() :
            response.Content.ReadAsStringAsync().ToObservable());
}

相比較 Task API,Rx.NET 代碼簡練可是比較難理解。async

Main 方法

public static void Main(string[] args)
{
    {
        var dataStore = new PostDataStoreByTask();
        Console.WriteLine(dataStore.GetPostAsString(1).Result);
        Console.WriteLine(dataStore.GetPostAsJson(1).Result);
        dataStore.GetPosts(2).Result.ToList().ForEach(Console.WriteLine);
        Console.WriteLine(dataStore.CreatePost(new Post
        {
            UserId = 101,
            Id = 0,
            Title = "test title",
            Body = "test body"
        }).Result);
        Console.WriteLine(dataStore.UpdatePost(new Post
        {
            UserId = 101,
            Id = 1,
            Title = "test title",
            Body = "test body"
        }).Result);
        Console.WriteLine(dataStore.DeletePost(1).Result);
    }
    {
        var dataStore = new PostDataStoreByRx();
        dataStore.GetPostAsString(1).Subscribe(Console.WriteLine);
        dataStore.GetPostAsJson(1).Subscribe(Console.WriteLine);
        dataStore.GetPosts(2).Do(Console.WriteLine).Subscribe();
        dataStore.CreatePost(new Post
        {
            UserId = 101,
            Id = 0,
            Title = "test title",
            Body = "test body"
        }).Subscribe(Console.WriteLine);
        dataStore.UpdatePost(new Post
        {
            UserId = 101,
            Id = 1,
            Title = "test title",
            Body = "test body"
        }).Subscribe(Console.WriteLine);
        dataStore.DeletePost(1).Subscribe(Console.WriteLine);
        Console.ReadKey();
    }
}

輸出結果

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 2, title = "qui est esse", body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"}
Post {userId = 101, id = 101, title = "test title", body = "test body"}
Post {userId = 101, id = 1, title = "test title", body = "test body"}
{}
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 2, title = "qui est esse", body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"}
Post {userId = 101, id = 1, title = "test title", body = "test body"}
Post {userId = 101, id = 101, title = "test title", body = "test body"}
{}
相關文章
相關標籤/搜索