ASP.NET MVC 开源项目Kigg解读(1)

Kigg是一个很好的ASP.NET MVC范例项目,本着研究的目的,对Kigg进行解读。

    • ASP.NET MVC
    • Linq To SQL
    • MS Patterns & Practices – Enterprise Library (Logging & Caching)
    • MS Patterns & Practices - Unity
    • jQuery
    • xUnit.net
    • Moq
    • HtmlAgilityPack
    • DotNetOpenId
    • jQuery UI & Markitup

Kigg介绍:

KiGG 是一个微软技术支持部门开发的Web 2.0 风格的社会新闻软件,采用如下的开发组件:

可以从http://kigg.codeplex.com/ 下载全部源代码

示例站点: KiGG v2.6 Beta

一、启动篇

就如一个操作系统,开机时需要boot,于是kigg也从boot开始!

在Kigg.Core中,定义了IBootstrapperTask接口

public interface IBootstrapperTask
{
void Execute();
}

纵观Kigg的源代码,我们可以发现共有4个Boot Task,如下图所示:

这4个task分别是创建默认用户,注册Controller工厂,注册路由,和启动后台任务(Background Tasks)

怎么在系统启动的时候调用IBootstrapperTask?Kigg专门建立了一个静态类Bootstrapper:

   1:    public static class Bootstrapper
   2:      {
   3:          static Bootstrapper()
   4:          {
   5:              try
   6:              {
   7:                  IoC.InitializeWith(new DependencyResolverFactory());
   8:              }
   9:              catch (ArgumentException)
  10:              {
  11:                  // Config file is Missing
  12:              }
  13:          }
  14:   
  15:          public static void Run()
  16:          {
  17:              IoC.ResolveAll<IBootstrapperTask>().ForEach(t => t.Execute());
  18:          }
  19:      }

在该类的静态构造函数里,进行的是IOC(这里用的是Unity)的初始化工作。同时,Bootstrapper类还有个Run方法,该方法调用IOC Resolve所有实现了IBootstrapperTask接口的任务,然后ForEach(一个扩展方法,遍历集合)每个任务并Execute。

于是,我们在Kigg的GlobalApplication里看到了华丽丽的Bootstrapper.Run();

   1:      public class GlobalApplication : HttpApplication
   2:      {
   3:          public static void OnStart()
   4:          {
   5:              Bootstrapper.Run();
   6:              Log.Info("Application Started");
   7:          }
   8:     } 

二、后台任务

其实分析IBootstrapperTask的初衷是对Kigg的后台任务(BackgroundTask)感兴趣:

   1:  public interface IBackgroundTask
   2:      {
   3:          bool IsRunning
   4:          {
   5:              get;
   6:          }
   7:   
   8:          void Start();
   9:   
  10:          void Stop();
  11:      }

在Kigg中,共有5种后台任务:

这些后台任务的开启,是在实现了IBootstrapperTask接口的StartBackgroundTasks中开启的:

   1:      public class StartBackgroundTasks : IBootstrapperTask
   2:      {
   3:          private readonly IBackgroundTask[] _tasks;
   4:   
   5:          public StartBackgroundTasks(IBackgroundTask[] tasks)
   6:          {
   7:              Check.Argument.IsNotEmpty(tasks, "tasks");
   8:   
   9:              _tasks = tasks;
  10:          }
  11:   
  12:          public void Execute()
  13:          {
  14:              _tasks.ForEach(t => t.Start());
  15:          }
  16:      }

StartBackgroundTasks 类的构造函数参数是通过IOC搞定的(后面会单独介绍IOC)

三、事件聚合器IEventAggregator

在分析BackgroundTask的代码时,发现所有的BackgroundTask都继承于BaseBackgroundTask:

在查看BaseBackgroundTask时,发现了令人惊喜的东西——IEventAggregator

EventAggregator是何许玩意呢?按字面意思,事件聚合器?姑且这么叫吧!这个接口只有一个方法:

public interface IEventAggregator

{

TEventType GetEvent<TEventType>() where TEventType : BaseEvent;

}

作用是获取一个继承BaseEvent的事件(Event).

为了弄清楚EventAggregator到底有什么用,我们先来看看与BaseEvent相关的几个类:

首先是一个事情订阅接口,包含一个订阅Token,一个获取可执行函数的方法。

   1:  public interface IEventSubscription
   2:      {
   3:          SubscriptionToken SubscriptionToken
   4:          {
   5:              get;
   6:              set;
   7:          }
   8:   
   9:          Action<object[]> GetExecutionStrategy();
  10:      }

订阅Token,实现了IEquatable接口,可以进行比较,这里的Token没什么特别的作用,仅仅用来标识一个订阅,这样在移除订阅的时候通过Token能方便的找到并移除

   1:      public class SubscriptionToken : IEquatable<SubscriptionToken>
   2:      {
   3:          private readonly Guid _token = Guid.NewGuid();
   4:   
   5:          [DebuggerStepThrough]
   6:          public bool Equals(SubscriptionToken other)
   7:          {
   8:              return (other != null) && Equals(_token, other._token);
   9:          }
  10:   
  11:          [DebuggerStepThrough]
  12:          public override bool Equals(object obj)
  13:          {
  14:              return ReferenceEquals(this, obj) || Equals(obj as SubscriptionToken);
  15:          }
  16:   
  17:          [DebuggerStepThrough]
  18:          public override int GetHashCode()
  19:          {
  20:              return _token.GetHashCode();
  21:          }
  22:   
  23:          [DebuggerStepThrough]
  24:          public override string ToString()
  25:          {
  26:              return _token.ToString();
  27:          }
  28:      }

再来看最为关键的BaseEvent

   1:      /// <summary>
   2:      /// 事件基类
   3:      /// </summary>
   4:      public abstract class BaseEvent
   5:      {
   6:          private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();
   7:   
   8:   
   9:          /// <summary>
  10:          /// 订阅者
  11:          /// </summary>
  12:          protected ICollection<IEventSubscription> Subscriptions
  13:          {
  14:              [DebuggerStepThrough]
  15:              get
  16:              {
  17:                  return _subscriptions;
  18:              }
  19:          }
  20:   
  21:          /// <summary>
  22:          /// 订阅
  23:          /// </summary>
  24:          /// <param name="eventSubscription"></param>
  25:          /// <returns></returns>
  26:          protected virtual SubscriptionToken Subscribe(IEventSubscription eventSubscription)
  27:          {
  28:              eventSubscription.SubscriptionToken = new SubscriptionToken();
  29:   
  30:              lock (_subscriptions)
  31:              {
  32:                  _subscriptions.Add(eventSubscription);
  33:              }
  34:   
  35:              return eventSubscription.SubscriptionToken;
  36:          }
  37:   
  38:          protected virtual void Publish(params object[] arguments)
  39:          {
  40:              List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
  41:   
  42:              foreach (var executionStrategy in executionStrategies)
  43:              {
  44:                  executionStrategy(arguments);
  45:              }
  46:          }
  47:   
  48:          public virtual void Unsubscribe(SubscriptionToken token)
  49:          {
  50:              lock (_subscriptions)
  51:              {
  52:                  IEventSubscription subscription = _subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
  53:   
  54:                  if (subscription != null)
  55:                  {
  56:                      _subscriptions.Remove(subscription);
  57:                  }
  58:              }
  59:          }
  60:   
  61:          public virtual bool Contains(SubscriptionToken token)
  62:          {
  63:              lock (_subscriptions)
  64:              {
  65:                  IEventSubscription subscription = _subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
  66:   
  67:                  return (subscription != null);
  68:              }
  69:          }
  70:   
  71:          private List<Action<object[]>> PruneAndReturnStrategies()
  72:          {
  73:              List<Action<object[]>> returnList = new List<Action<object[]>>();
  74:   
  75:              lock (_subscriptions)
  76:              {
  77:                  for (int i = _subscriptions.Count - 1; i >= 0; i--)
  78:                  {
  79:                      Action<object[]> subscriptionAction = _subscriptions[i].GetExecutionStrategy();
  80:   
  81:                      if (subscriptionAction == null)
  82:                      {
  83:                          _subscriptions.RemoveAt(i);
  84:                      }
  85:                      else
  86:                      {
  87:                          returnList.Add(subscriptionAction);
  88:                      }
  89:                  }
  90:              }
  91:   
  92:              return returnList;
  93:          }
  94:      }

BaseEvent包含了Subscribe,Publish这两个对事件进行处理的关键方法:

Subscribe时会添加一个IEventSubscription,Publish时会执行所有IEventSubscription中的方法。

回过头来,再来看BaseBackgroundTask:

   1:   public abstract class BaseBackgroundTask : IBackgroundTask
   2:      {
   3:          private readonly IEventAggregator _eventAggregator;
   4:   
   5:          protected BaseBackgroundTask(IEventAggregator eventAggregator)
   6:          {
   7:              Check.Argument.IsNotNull(eventAggregator, "eventAggregator");
   8:   
   9:              _eventAggregator = eventAggregator;
  10:          }
  11:   
  12:          public bool IsRunning
  13:          {
  14:              get;
  15:              private set;
  16:          }
  17:   
  18:          protected internal IEventAggregator EventAggregator
  19:          {
  20:              [DebuggerStepThrough]
  21:              get
  22:              {
  23:                  return _eventAggregator;
  24:              }
  25:          }
  26:   
  27:          public void Start()
  28:          {
  29:              OnStart();
  30:              IsRunning = true;
  31:          }
  32:   
  33:          public void Stop()
  34:          {
  35:              OnStop();
  36:              IsRunning = false;
  37:          }
  38:   
  39:          protected abstract void OnStart();
  40:   
  41:          protected abstract void OnStop();
  42:   
  43:          protected internal SubscriptionToken Subscribe<TEvent, TEventArgs>(Action<TEventArgs> action) where TEvent : BaseEvent<TEventArgs> where TEventArgs : class
  44:          {
  45:              return _eventAggregator.GetEvent<TEvent>().Subscribe(action, true);
  46:          }
  47:   
  48:          protected internal void Unsubscribe<TEvent>(SubscriptionToken token) where TEvent : BaseEvent
  49:          {
  50:              _eventAggregator.GetEvent<TEvent>().Unsubscribe(token);
  51:          }
  52:      }

注意下 Subscribe和Unsubscribe方法,这两个方法通过eventAggregator获取特定的TEvent,实现事件的定订阅和解除订阅。

然后再来看一个具体的Task,比如PingServer:

PingServer继承BaseBackgroundTask ,需要实现OnStart和OnStop,PingServer的作用是在发布一篇story的时候通知ping服务器,我更新了,你可以派你的爬虫过来了……因此,在OnStart方法中,Subscribe了story提交事件--StorySubmitEvent,并指定用StorySubmitted方法来处理这个事件,因此StorySubmitted方法只需要实现发送ping的代码就可以了。

   1:   
   2:          protected override void OnStart()
   3:          {
   4:              if (!IsRunning)
   5:              {
   6:                  _storySubmitToken = Subscribe<StorySubmitEvent, StorySubmitEventArgs>(StorySubmitted);
   7:                  _storyApproveToken = Subscribe<StoryApproveEvent, StoryApproveEventArgs>(StoryApproved);
   8:              }
   9:          }
  10:   
  11:          protected override void OnStop()
  12:          {
  13:              if (IsRunning)
  14:              {
  15:                  Unsubscribe<StorySubmitEvent>(_storySubmitToken);
  16:                  Unsubscribe<StoryApproveEvent>(_storyApproveToken);
  17:              }
  18:          }
  19:   
  20:          internal void StorySubmitted(StorySubmitEventArgs eventArgs)
  21:          {
  22:              SendPing();
  23:          }
  24:   
  25:          internal void StoryApproved(StoryApproveEventArgs eventArgs)
  26:          {
  27:              SendPing();
  28:          }

光有订阅是不行的,同学们,还需要有发布才行!关于发布,看看StoryService就可以了,在这个service的Create函数中有这么一段代码:

   1:  _eventAggregator.GetEvent<StorySubmitEvent>().Publish(new StorySubmitEventArgs(story,detailUrl));

OMG,这就是发布吗?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

Spring Boot 2.0-WebFlux framework

1、介绍 1.1 什么是响应式编程(Reactive Programming)? 简单来说,响应式编程是针对异步和事件驱动的非阻塞应用程序,并且需要少量线程来垂...

3785
来自专栏一个会写诗的程序员的博客

《Kotin 极简教程》第14章 使用 Kotlin DSL第14章 使用 Kotlin DSL《Kotlin极简教程》正式上架:

我们在前面的章节中,已经看到了 Kotlin DSL 的强大功能。例如Gradle 的配置文件 build.gradle (Groovy),以及前面我们涉及到的...

431
来自专栏小灰灰

Batik渲染png图片异常的bug修复

Batik渲染png图片异常的bug修复 batik是apache的一个开源项目,可以实现svg的渲染,后端借助它可以比较简单的实现图片渲染,当然和java一贯...

1749
来自专栏微服务

ASP.NET Core 依赖注入

一、什么是依赖注入(Denpendency Injection) 这也是个老身常谈的问题,到底依赖注入是什么? 为什么要用它? 初学者特别容易对控制反转IOC(...

2828
来自专栏跟着阿笨一起玩NET

通过 INotifyPropertyChanged 实现观察者模式

当属性改变时,它可以通知客户端,并进行界面数据更新.而我们不用写很多复杂的代码来更新界面数据,这样可以做到方法简洁而清晰,松耦合和让方法变得更通用.可用的地方太...

371
来自专栏Android相关

AAC---LiveData

LiveData是一个与Activity/Fragment生命周期相关(lifecycle-aware)的Observer类。而这种相关性(awareness ...

732
来自专栏数据小魔方

教你如何优雅的用R语言调用有道翻译

最近刚发现了个有趣的包,一个R语言发烧友开发了R语言与有道在线翻译的接口,可能这位大神也是一个受够了每天打开网页狂敲键盘查词的罪,索性自己动手,从此丰衣足食。 ...

2033
来自专栏漏斗社区

工具| 手把手教你制作信息收集器之网站备案号

本期任务: 1.掌握备案号的收集 。 2.练习从http返回包中获取信息的能力。 3.所需工具: pip,http请求库:requests库,匹配库:r...

36310
来自专栏张善友的专栏

自定义Unity对象生命周期管理集成ADO.NET Entity Framework

在Unity中,从Unity 取得的实例为 Transient。如果你希望使用多线程方式,就需要在组成时使用lifecycle参数,这时候取出的组件就不再是同一...

2748
来自专栏用户2442861的专栏

初步学习Qt布局

在一个Widget中,Qt布局管理系统提供了一个简单而有效的方式来自动组织子widget,以保证他们能够很好地利用可用空间。

491

扫码关注云+社区