今天学个比较难的——订阅
订阅功能:运行后,打开两个浏览器,同样在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,创建
然后你发现右下边那张图出现了下面那张图的情况,另一个依旧在监听模式
好了,今天的订阅模式搞定了,多回去敲敲