1. 程式人生 > >GraphQL教程(四) .net core api

GraphQL教程(四) .net core api

今天學個比較難的——訂閱
訂閱功能:執行後,開啟兩個瀏覽器,同樣在GraphQL介面上操作。其中一個開啟監聽,另一個建立,用MovicRating事件將兩者關聯,建立成功後,監聽的介面也將顯示相關訊息。
總共有五個步驟
1.在Movie資料夾建立事件屬性MovieEvent,型別MovieEventType
2.在Service資料夾建立介面IMovieEventService與實現介面MovieEventService類
3.修改MovieSerivce類的建立程式,在其中新增事件
4.Schema資料夾實現訂閱方法MovieSubscription類
5.將MovieSubscription新增到MovieSchema類,併到startup類註冊相關服務

1.在Movie資料夾建立事件屬性MovieEvent,型別MovieEventType

MovieEvent類

using System;

namespace GraphStudy.Movies.Movies
{
    public class MovieEvent
    {
        public MovieEvent()
        {
            //初始化系統的一個新例項。Guid結構。
            Id = Guid.NewGuid();
        }
        public Guid Id { get; set; }
        public int MovieId { get; set; }
        public string Name { get; set; }
        public DateTime TimeStamp { get; set; }
        public MovieRating MovieRating { get; set; }
    }
}

這個是與事件觸發相關的類,並不是Movie類的那種格式了

Guid解釋:GUID(全域性統一識別符號)是指在一臺機器上生成的數字,
它保證對在同一時空中的所有機器都是唯一的。通常平臺會提供生成GUID的API。
生成演算法很有意思,用到了乙太網卡地址、納秒級時間、晶片ID碼和許多可能的數字。
GUID 的格式為“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”
GUID的唯一缺陷在於生成的結果串會比較大。”

MovieEventType類

using GraphQL.Types;
using GraphStudy.Movies.Movies;

namespace GraphStudy.Movies.Schema
{
    public class MovieEventType:ObjectGraphType<MovieEvent>
    {
        public MovieEventType()
        {
            Name = "MovieEventType";

            Field(x => x.Id, type: typeof(IdGraphType));//guid有點特殊
            Field(x => x.Name);
            Field(x => x.MovieId);
            Field(x => x.TimeStamp);
            Field(x => x.MovieRating, type: typeof(MovieRatingEnum));
        }
    }
}

這個還是老樣子,不過要注意有些特殊型別要額外提出來,不然執行會出錯

2.在Service資料夾建立介面IMovieEventService與實現介面MovieEventService類

IMovieEventService類

using GraphStudy.Movies.Movies;
using System;
using System.Collections.Concurrent;

namespace GraphStudy.Movies.Services
{
    //Service釋出事件,客戶端可以接受這個時間的通知
    public interface IMovieEventService
    {
        //首先需要個合集,ConcurrentStack為併發集合,與執行緒相關
        ConcurrentStack<MovieEvent> AllEvent { get; }

        //新增異常的方法
        void AddError(Exception ex);

        //新增MovieEvent方法
        MovieEvent AddEvent(MovieEvent e);

        //定義stream方法
        IObservable<MovieEvent> EventStream();

        //c#中的Observer和IObservable用於觀察者與事件、代理
    }
}

MovieEventService類

using GraphStudy.Movies.Movies;
using System;
using System.Collections.Concurrent;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace GraphStudy.Movies.Services
{
    public class MovieEventService : IMovieEventService
    {
        //ISubject:表示既是可觀察序列又是觀察者的物件。
        //ReplaySubject意思為無論訂閱者什麼時候訂閱都會將以前釋出的內容釋出給他,並初始化ISubject物件
        private readonly ISubject<MovieEvent> _eventStream=new ReplaySubject<MovieEvent>();

        //ConcurrentStack:後進的執行緒後出
        public ConcurrentStack<MovieEvent> AllEvent { get; }

        public MovieEventService()
        {
            AllEvent=new ConcurrentStack<MovieEvent>();
        }

        public void AddError(Exception ex)
        {
            _eventStream.OnError(ex);
        }

        public MovieEvent AddEvent(MovieEvent e)
        {
            //Push推送
            AllEvent.Push(e);
            //OnNext:當前訊息通知
            _eventStream.OnNext(e);
            return e;
        }

        //IObservable<T>:通知程式觀察者將接到訊息通知
        //T:代表觀察員,接收通知物件
        public IObservable<MovieEvent> EventStream()
        {
            //AsObservable:隱藏源序列身份的可觀察序列
            return _eventStream.AsObservable();
        }
    }
}

3.修改MovieSerivce類的建立程式,在其中新增事件

using GraphStudy.Movies.Movies;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace GraphStudy.Movies.Services
{
    public class MovieService : IMovieService
    {
        //因為我們需要建立子列表,所以一要用到IList
        private readonly IList<Movie> _movie;
        private readonly IMovieEventService _movieEventService;

        public MovieService(IMovieEventService movieEventService)
        {
            _movieEventService = movieEventService;

            _movie=new List<Movie>
            {
                #region 電影列表
    
                new Movie
                {
                    Id = 1,
                    Name = "肖申克的救贖The Shawshank Redemption",
                    Company = "美國",
                    MovieRating = MovieRating.G,
                    ActorId = 1,
                    ReleaseDate = new DateTime(1994-10-14)
                },
                new Movie
                {
                    Id = 2,
                    Name = "這個殺手不太冷 Léon ",
                    Company = "法國",
                    MovieRating = MovieRating.NC17,
                    ActorId = 2,
                    ReleaseDate = new DateTime(1994-09-14)
                },
                new Movie
                {
                    Id = 3,
                    Name = "三傻大鬧好萊塢",
                    Company = "印度",
                    MovieRating = MovieRating.PG,
                    ActorId = 3,
                    ReleaseDate = new DateTime(2011-12-08)
                },
                new Movie
                {
                    Id = 4,
                    Name = "功夫",
                    Company = "美國",
                    MovieRating = MovieRating.G,
                    ActorId = 4,
                    ReleaseDate = new DateTime(2004-12-23)
                }
                #endregion
            };
        }


        public Task<Movie> CreateAsync(Movie movie)
        {
            _movie.Add(movie);

            //建立時釋出事件
            var movieEvent = new MovieEvent
            {
                Name = $"Add Movie",
                MovieId = movie.Id,
                MovieRating = movie.MovieRating,
                TimeStamp = DateTime.Now
            };
            _movieEventService.AddEvent(movieEvent);

            return Task.FromResult(movie);
        }

        public Task<IEnumerable<Movie>> GetAsyncs()
        {
            return Task.FromResult(_movie.AsEnumerable());
        }

        public Task<Movie> GetByIdAsync(int id)
        {
            //在這裡需要做個判斷這個id是否存在
            var movie = _movie.SingleOrDefault(x => x.Id == id);
            if (movie == null)
            {
                throw new ArgumentException(String.Format("Movie ID {0} 不正確", id));
            }

            return Task.FromResult(movie);
        }
    }
}

修改了兩個地方,可自行上下程式碼對照

//第一個
private readonly IMovieEventService _movieEventService;
public MovieService(IMovieEventService movieEventService)

//第二個
public Task<Movie> CreateAsync(Movie movie)
        {
            _movie.Add(movie);

            //建立時釋出事件
            var movieEvent = new MovieEvent
            {
                Name = $"Add Movie",
                MovieId = movie.Id,
                MovieRating = movie.MovieRating,
                TimeStamp = DateTime.Now
            };
            _movieEventService.AddEvent(movieEvent);

看不懂沒關係,多抄幾遍,炒得多了,慢慢就懂了

4.Schema資料夾實現訂閱方法MovieSubscription類

using GraphQL.Resolvers;
using GraphQL.Subscription;
using GraphQL.Types;
using GraphStudy.Movies.Movies;
using GraphStudy.Movies.Services;

namespace GraphStudy.Movies.Schema
{
    public class MovieSubscription:ObjectGraphType
    {
        private readonly IMovieEventService _movieEventService;

        public MovieSubscription(IMovieEventService movieEventService)
        {
            _movieEventService = movieEventService;
            Name = "Subscription";

            //這裡注意。以前的field不管用,EventStreamFieldType為AddField的引數
            AddField(new EventStreamFieldType
            {
                Name = "movieEvent",
                //Arguments給AddField新增引數
                Arguments = new QueryArguments(new QueryArgument<ListGraphType<MovieRatingEnum>>
                {
                    Name = "movieRatings"
                }),
                Type = typeof(MovieEventType),//這裡的型別與GraphQL文件裡的型別相對應
                Resolver = new FuncFieldResolver<MovieEvent>(ResolveEvent),//傳遞ResolveEvent方法
                Subscriber = new EventStreamResolver<MovieEvent>(Subscribe)//傳遞Subscribe方法
            });

        }

        private MovieEvent ResolveEvent(ResolveFieldContext context)
        {
            var movieEvent = context.Source as MovieEvent;
            return movieEvent;
        }

        private IObservable<MovieEvent> Subscribe(ResolveEventStreamContext context)
        {
            //取得列舉的集合,這裡的name應與上面的Arguments的name對應,new List<MovieRating>()給其預設值。空值
            var ratingList = context.GetArgument<IList<MovieRating>>("movieRatings", new List<MovieRating>());

            //if為過濾操作,any()確定ratingList是否含有元素
            if (ratingList.Any())
            {
                MovieRating ratings = 0;
                foreach (var rating in ratingList)
                {
                    ratings = rating;
                }

                return _movieEventService.EventStream().Where(e => (e.MovieRating & ratings) == e.MovieRating);
            }
            else
            {
                return _movieEventService.EventStream();
            }
        }
 
    }
}

有些複雜,抄過去就是了,回頭多抄幾遍

5.將MovieSubscription新增到MovieSchema類,併到startup類註冊相關服務

MovieSchema類

using GraphQL;

namespace GraphStudy.Movies.Schema
{
    public class MovieSchema:GraphQL.Types.Schema
    {
        public MovieSchema(IDependencyResolver dependencyResolver, 
            MoviesQuery moviesQuery,
            MoviesMutation moviesMutation,
            MovieSubscription movieSubscription)
        {
            DependencyResolver = dependencyResolver;
            Query = moviesQuery;
            Mutation = moviesMutation;
            Subscription = movieSubscription;
        }
    }
}

startup類註冊

		services.AddSingleton<MovieEventType>();
            services.AddSingleton<IMovieEventService, MovieEventService>();
            services.AddSingleton<MovieSubscription>();

怎麼註冊就不用多說了吧

最後檢視訂閱
在這裡插入圖片描述
先點選右邊兩個,進入監聽模式
後點擊左邊那個create,建立
然後你發現右下邊那張圖出現了下面那張圖的情況,另一個依舊在監聽模式
在這裡插入圖片描述

好了,今天的訂閱模式搞定了,多回去敲敲