[C#]使用rx.net來幫助完成主動通知和訂閱的功能

[C#]使用rx.net來幫助完成主動通知和訂閱的功能

前言

rxjs已經算是在前端很紅的一個主題了,不過相對來說rx.net則比較少看到有人討論,其實rx提供了很多類似functional programming的概念,甚至還提供了類似event emit的想法,也有更多處理多個非同步的強大operator,而今天想要來筆記一下,如何使用rx來完成推播和訂閱的需求,而這邊的主題最主要會使用到的就是subject這個名詞。

導覽

  1. 安裝rx.net
  2. 情境模擬
  3. 完成訂閱
  4. 發送主動通知
  5. 結論

安裝rx.net

首先我們需要在visual studio安裝rx.net,開起nuget並搜尋reactive的關鍵字,然後如下圖下載

此舉會同時幫你安裝core和interface的部份,而我們會使用到的很多operator都是包裝在Linq裡面,同時也會看到很多已經熟悉的lambda語法,而如果爬文比較多看到的都是rxjs的介紹,就連官方對rx.net的說明也少得可憐,所以如果需要使用到rx.net的話,必須覺悟可能得花很多時間自行摸索,而如果對很多觀念不懂的話,如果對前端熟悉的話,可以由rxjs來了解觀念。

情境模擬

試想一下我們傳統在做快取資料的時候,都是用時間性來達成,但如果我們有新資料想要通知快取更新的話,目前有另一種redis的做法,預設就有主動通知的api可以使用,但redis快取比較適合在很多系統的共享資料情境,如果我們只是想要在各別系統快取的話,還是放在各系統的ap上比較適合,而這時候我們其實就可以使用rx.net來達成即時通知快取更新資料的做法。

首先我來摸擬一個會員登入資料,當有其他會員登入資料的時候,api會即時傳給前端總共有哪些會員登入,先來實做一個Member.cs的dto

public class MemberModel                   
{                                          
    public int Id { get; set; }            
    public string Name { get; set; }       
}                                          

接著我們先做兩筆假資料,然後使用memory cache的方式來做快取,程式碼如下

    public class CacheService
    {
        ObjectCache cache = MemoryCache.Default;

        public void Init()
        {
            if (cache.Get("Members") == null)
            {
                var Members = new List<MemberModel>
                {
                    new MemberModel {Id=1,Name="kin" },
                    new MemberModel {Id=2,Name="anson" },
                };
                var policy = new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable };
                cache.Add("Members", Members, policy);
            }
        }

        public List<MemberModel> GetMembers()
        {
            return cache.Get("Members") as List<MemberModel>;
        }
    }

因為我打算在系統一開始的時候,就先把快取寫進去了,所以我選擇在Global.asax裡面去Init快取資料

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            CacheService service = new CacheService();
            service.Init();
            BundleConfig.RegisterBundles(BundleTable.Bundles);

        }
    }

接著我是使用swagger直接測試,以確保資料是有寫進memory裡面

    public class ValuesController : ApiController
    {
        CacheService _service = new CacheService();
        // GET: api/Values
        public IHttpActionResult Get()
        {
            return Ok(_service.GetMembers());
        }
    }

結果如下圖

完成訂閱

接著我們需要把寫快取的時機點,改成使用訂閱的方式來實現,也就是我們去訂閱Subject,而何時要去快取的機制則改成用rx來實現,首先來看一下我們如何實做這個部份,新增一支EventSubjects.cs

    public static class EventSubjects
    {
        public static Subject<MemberModel> SubjectMembers { get; set; } = new Subject<MemberModel>();
    }

接著我希望訂閱的時候一樣在系統一開始就去執行,所以一樣在Global.asax動手腳

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            CacheService service = new CacheService();
            service.Init();
            service.SubscribeSubjects(); //新增訂閱subject
            BundleConfig.RegisterBundles(BundleTable.Bundles);

        }
    }

然後替CacheService新增訂閱的方法

    public class CacheService
    {
        ObjectCache cache = MemoryCache.Default;
        CacheItemPolicy policy = new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable };

        public void Init()
        {
            if (cache.Get("Members") == null)
            {
                var Members = new List<MemberModel>
                {
                    new MemberModel {Id=1,Name="kin" },
                    new MemberModel {Id=2,Name="anson" },
                };
                cache.Add("Members", Members, policy);
            }
        }

       /// <summary>
       /// 訂閱的方法
       /// </summary>
        public void SubscribeSubjects()
        {
            EventSubjects.SubjectMembers.Subscribe(x =>
            {
                var result = GetMembers();
                var member=result.FirstOrDefault(r => r.Id == x.Id);
                if (member == null) result.Add(x);
                cache.Set("Members", result, policy);
            });
        }

        public List<MemberModel> GetMembers()
        {
            return cache.Get("Members") as List<MemberModel>;
        }
    }

大功告成了,自此之後什麼時候重寫快取就是用訂閱的方式來實現了

發送主動通知

這邊則是關鍵點了,當我們打web api的post時候,假設成功就去觸發subject去做主動通知

    public class ValuesController : ApiController
    {
        CacheService _service = new CacheService();
        // GET: api/Values
        public IHttpActionResult Get()
        {
            return Ok(_service.GetMembers());
        }

        public void Post(MemberModel model)
        {
            EventSubjects.SubjectMembers.OnNext(model);
        }
    }

整個完成之後我們就可以測試看看,是否會如預期的當我新增資料的時候,資料是否有順利寫進快取。

一開始的資料

接著打post

重新再Get一次去拉快取資料

確認我們是使用主動通知的方式,去通知寫進快取資料了。

結論

研究rx.net的時候,文件確實很缺乏,這就造成要研究這個技術確實不是那麼容易入門和學習,畢竟文件不全相關文章也不多,但相對性的來說rxjs則是有很多相關應用,而rxjs光是operator的相關應用就博大精深了,所以有興趣的筆者就請至rxjs30天這系列文章去做一些深入了解(https://ithelp.ithome.com.tw/articles/10186104),而這個連結更是用圖示的方式讓你理解各種operator的應用(http://rxmarbles.com/),此篇沒什麼用到operator的部份,主要是使用到subject的特性,如果對此篇文章有任何疑問或意見,再請回覆囉。