本文章是由機器翻譯。

MVVM

使用 MVVM 撰寫可測試的展示層

Brent Edwards

下载代码示例

與傳統的應用程式從 Windows 表單天,測試的標準做法是佈局視圖,在視圖的代碼隱藏,然後作為一個測試回合該應用程式中編寫代碼。 幸運的是,事情已經有點自那時以來。

Windows Presentation Foundation(WPF) 的出現帶來了概念的資料繫結到一個全新的水準。 它允許調用模型-視圖-ViewModel (MVVM) 進化新設計模式。 使用 MVVM 允許您將演示文稿邏輯分開從實際的演示文稿。 基本上,這意味著大部分為您可以避免在視圖的代碼隱藏中編寫代碼。

這是一個重大的改進,對於那些興趣開發可測試應用程式。 現在而不是您的演示文稿邏輯附加到該視圖的代碼隱藏,有它自己的生命週期變得複雜測試,可以使用普通的舊 CLR 物件 (POCO)。 查看模型的視圖不會的生命週期約束不了 您只可以具現化視圖模型中的單元測試和測試走。

在本文中,我會看如何處理書面使用 MVVM 的應用程式的可測試演示文稿層。 説明說明我的做法,我就會包括示例代碼從一個開源框架一寫了,叫美好,並隨附的應用程式範例,稱為吸引著讀者。 此框架和示例应用程序可在 GitHub 上找到 (github.com/brentedwards/Charmed)。

我在我 2013 年 7 月的文章介紹的迷人框架 (msdn.microsoft.com/magazine/dn296512) 作為 Windows 8 的框架和應用程式範例。 然後在我 9 月 2013 年第條 (msdn.microsoft.com/magazine/dn385706),我討論了使它跨­作為一個 Windows 8 和 Windows Phone 8 框架和應用程式範例的平臺。 在這兩篇文章,我說過我作出的決定,以保持應用程式可測試。 現在,我會重新考慮這些決定和顯示我實際上怎樣測試應用程式。 這篇文章使用 Windows 8 和 Windows Phone 8 代碼示例,但您可以適用于任何類型的應用程式的概念和技術。

关于示例应用程序

應用程式範例闡釋如何編寫可測試演示文稿層稱為吸引著讀者。 神往的讀卡機是一個簡單的博客讀者應用程式,在 Windows 8 和 Windows Phone 8 上工作。 它有來說明我想要覆蓋的關鍵點所需的最小功能。 它是跨平臺,並在這兩個平臺上,與 Windows 8 的應用程式利用了一些 Windows 8 特定功能的異常的作品大多是一樣。 雖然這款應用程式是基本的有足夠的單元測試的功能。

什麼單元測試?

單元測試背後的理念是代碼的採取離散塊大塊 (單位) 和寫入測試方法,使用的代碼按預期的方式,然後進行測試,看看他們得到預期的結果。 使用某種測試線束框架運行此測試代碼。 有與Visual Studio2012年一起工作的幾個測試線束框架。 在示例代碼中,使用 MSTest,這是內置Visual Studio2012年 (和更早)。 目標是要有一個單個單元測試方法目標一個特定的方案。 有時需要幾個單元測試方法,涵蓋所有方案,你期望你的方法或屬性,以容納。

單元測試方法應遵循一致的格式,使它更容易為其他開發人員理解。 按以下格式通常被認為是最佳做法:

  1. Arrange
  2. 法 》
  3. Assert

第一,可能有一些您需要編寫創建測試類的實例的安裝程式碼以及它可能具有的任何依賴項。 這是單元測試的排列部分。

單位後做測試設定為實際測試階段,您可以執行的方法或屬性問題。 這是測試的 Act 部分。 您可以執行該方法或屬性問題與參數 (如果適用) 設置在排列節期間。

最後,當你已經執行的方法或屬性問題,測試需要驗證該方法或屬性做到底它是怎麼做。 這是測試的 Assert 部分。 在 assert 階段,,調用 assert 方法來比較實際結果與預期的結果。 如果實際的結果是像預期的那樣,單元測試通過。 如果不是,則測試失敗。

繼此最佳實踐格式,我的測試通常看起來類似于以下內容:

[TestMethod]
public void SomeTestMethod()
{
  // Arrange
  // *Insert code to set up test
  // Act
  // *Insert code to call the method or property under test
  // Assert
  // *Insert code to verify the test completed as expected
}

有些人使用這種格式不包括評論出不同的部分 (排列/法/Assert) 測試的調用。 我更喜歡有分離的三個部分,只是為了確保我不失去什麼測試實際上上行事或我當只設立的跟蹤的意見。

有一套全面的精心編寫的單元測試的一個額外的好處是他們作為生活在應用程式的文檔。 新的開發人員查看您的代碼將能夠看到你如何預期股看不同場景下的使用的代碼測試覆蓋。

可測試性的規劃

如果你想要編寫一個可測試應用程式,它真的有助於提前計畫。 你會想要設計您的應用程式的體系結構,所以它是有利於單元測試。 靜態方法、 密封的類、 資料庫訪問和 Web 服務調用都可以使您的應用程式很難或不可能到單元測試。 然而,與一些規劃,您可以儘量減少他們對您的應用程式產生的影響。

吸引住讀者 app 是所有關于閱讀的博客文章。 下載這些博客文章涉及對 RSS 源,Web 訪問,它可以是相當難到單位測試該功能。 第一,你應該能夠運行單元測試,快速和處於斷開狀態。 可能依賴于在單元測試中的 Web 訪問違反了這些原則。

此外,一個單元測試應該是可重複的。 因為通常定期更新博客,它可能會成為不可能得到相同的資料隨著時間的推移下載。 我提前知道該單元測試的功能,將載入的博客就不可能如果我沒提前計畫。

這裡是我所知道的需要發生:

  1. MainViewModel 所需載入使用者想要同時讀取的所有博客帖子。
  2. 下載所需的那些博客文章從使用者已保存的各種 RSS 源。
  3. 下載完成後,發佈的博客文章需要被解析成資料傳輸物件 (Dto) 並提供到視圖。

如果我把放在 MainViewModel 代碼下載 RSS 源,它突然將負責不僅僅是資料載入和讓視圖資料繫結到它的顯示。 MainViewModel 將會負責制作 Web 請求和解析 XML 資料。 我到底想要什麼是有 MainViewModel 呼籲出一名傭工作出 Web 請求和解析 XML 資料。 MainViewModel 然後應表示要顯示的博客文章的物件的實例。 這些被稱為 Dto。

知道這一點,我可以抽象的 RSS 源載入和解析為一個 MainViewModel 可以調用的説明器物件。 然而這不是故事的結尾。 如果我只是創建不會 RSS 的説明器類飼料的資料的工作,我為 MainViewModel 寫此功能周圍任何單元測試還會調用的説明器類,做網站的訪問。 如前所述,這違背了單元測試的目標。 所以,我需要進一步採取它邁出的一步。

如果我創建一個介面的 rss 訂閱來源資料載入功能,我可以有我的看法模型而不是具體的類介面的工作。 然後我可以提供的介面的不同實現方法,當我運行而不是運行該應用程式的單元測試。 這是嘲笑背後的概念。 當我真正運行應用程式時,我想要的實際物件,載入的真正的 RSS 來源資料。 當我運行的單元測試時,我想要一個 mock 物件,只是假裝載入 RSS 資料,但從來沒有真正轉到 Web 上。 Mock 物件可以創建一致的資料,是可重複,永遠不會改變。 然後,我單元測試可以知道到底什麼指望每次。

銘記這一點,我載入的博客文章的介面看起來像這樣:

public interface IRssFeedService
{
  Task<List<FeedData>> GetFeedsAsync();
}

那裡是只有一個方法,GetFeedsAsync,MainViewModel 可以使用載入的博客發送資料。 MainViewModel 並不需要關心 IRssFeedService 將資料的載入,或者它如何分析資料。 所有 MainViewModel 需要關心都是 GetFeedsAsync 將非同步返回博客發佈資料的調用。 鑒於跨平臺應用程式的性質,這一點尤其重要。

Windows 8 和 Windows Phone 8 有下載和解析 RSS 來源資料的不同方式。 通過 IRssFeedService 介面製作並讓 MainViewModel 進行交互,而不是直接下載博客供稿,我避免強制 MainViewModel,有多個實現的相同的功能。

使用依賴關係注入,我可以確保在正確的時間給 MainViewModel IRssFeedService 正確的實例。 如前所述,我就會在單元測試期間提供 IRssFeedService 一個 mock 的實例。 一個有趣的使用 Windows 8 和 Windows Phone 8 代碼為基礎進行單元測試的討論是有不是任何真正的動態的嘲弄框架,目前可供這些平臺。 因為嘲諷是如何的一個重要部分我單元測試我的代碼,我不得不用我自己簡單的方式來創建類比考試。 由此產生的 RssFeedServiceMock 所示圖 1

圖 1 RssFeedServiceMock

public class RssFeedServiceMock : IRssFeedService
{
  public Func<List<FeedData>> GetFeedsAsyncDelegate { get; set; }
  public Task<List<FeedData>> GetFeedsAsync()
  {
    if (this.GetFeedsAsyncDelegate != null)
    {
      return Task.FromResult<List<FeedData>>(this.GetFeedsAsyncDelegate());
    }
    else
    {
      return Task.FromResult<List<FeedData>>(null);
    }
  }
}

基本上,我想要能夠提供一個委託,可以設置如何載入資料。 如果您不開發 Windows 8 或 Windows Phone 8,有很大的機會,你可以使用一個動態的嘲弄框架起訂量、 犀牛嘲笑或 NSubstitute 等。 是否你滾你自己類比考試,或使用一個動態的嘲弄框架,適用同樣的原則。

現在有的 IRssFeedService 介面創建和注入 MainViewModel,調用上的 IRssFeedService 介面和 RssFeed 的 GetFeedsAsync MainViewModel­ServiceMock 創建和準備使用,現在是時候到單元測試 IRssFeedService 與 MainViewModel 的相互作用。 我想要這種相互作用的試驗的重要方面是 MainViewModel 正確地調用 GetFeedsAsync 和飼料返回的資料是 MainViewModel,可通過 FeedData 屬性的飼料的資料相同。 在單元測試中圖 2 驗證了這對我來說。

圖 2 測試飼料載入功能

[TestMethod]
public void FeedData()
{
  // Arrange
  var viewModel = GetViewModel();
  var expectedFeedData = new List<FeedData>();
  this.RssFeedService.GetFeedsAsyncDelegate = () =>
    {
      return expectedFeedData;
    };
  // Act
  var actualFeedData = viewModel.FeedData;
  // Assert
  Assert.AreSame(expectedFeedData, actualFeedData);
}

每當我單元測試視圖模型 (或任何其他物件,就此而言),我想要給我實際的實例視圖模型測試的説明器方法。 查看模型很可能隨著時間推移,可能涉及不同的東西被注射進視圖模型,這意味著不同的建構函式參數的更改。 如果我在我所有的單元測試中創建視圖模型的一個新實例,然後更改建構函式的簽名,我必須更改單元測試以及它一大堆。 但是,如果創建一個説明器方法來創建新實例的視圖模型,我只需要在一個地方進行更改。 在這種情況下,GetViewModel 是説明器方法:

private MainViewModel GetViewModel()
{
  return new MainViewModel(this.RssFeedService, 
    this.Navigator, this.MessageBus);
}

我還使用 TestInitialize 屬性以確保在運行每個測試之前重新創建 MainViewModel 的依賴關係。 這裡是 TestInitialize 方法,使得這種情況發生:

[TestInitialize]
public void Init()
{
  this.RssFeedService = new RssFeedServiceMock();
  this.Navigator = new NavigatorMock();
  this.MessageBus = new MessageBusMock();
}

這一點,在此測試類中的每個單元測試將有所有類比考試全新實例運行時。

回望自己的測試,下面的代碼創建我預期的飼料的資料和設置 mock RSS 飼料的服務來回報它:

var expectedFeedData = new List<FeedData>();
this.RssFeedService.GetFeedsAsyncDelegate = () =>
  {
    return expectedFeedData;
  };

請注意我沒有到 expectedFeedData 的清單中添加任何實際的 FeedData 實例因為我不需要。 我只需要確保該清單本身是什麼 MainViewModel 結束了。 該清單實際上已至少為此測試 FeedData 實例中,會發生什麼也不在乎

測試的行為部分有以下行:

var actualFeedData = viewModel.FeedData;

然後,我可以斷言 actualFeedData 是 expectedFeedData 的同一實例。 如果他們不是同一個實例,然後 MainViewModel 不做它的工作和單元測試應失敗。

Assert.AreSame(expectedFeedData, actualFeedData);

可測試導航

我想要測試的應用程式範例的另一個重要是導航。 施了魔法的讀卡機應用程式範例使用基於模型的視圖導航因為我想要的視圖和視圖模型分開。 神往的讀者是一個跨平臺應用程式和創建視圖模型用於在兩個平臺上,儘管意見需要不同的 Windows 8 和 Windows Phone 8。 有一些原因為何,但它歸結為每個平臺都有略微不同的 XAML 的事實。 因此,我不想知道我的看法,我查看模型水污濁。

抽象介面背後的導航功能原因有幾個是的解決辦法。 第一個和最重要是每個平臺都有導航,在不同的班級,我不想讓我的視圖模型,也不用擔心這些差異。 此外,在這兩種情況下,不能嘲笑所涉及的導航類。 所以,我摘錄這些問題從視圖模型和創建的 INavigator 介面:

public interface INavigator
{
  bool CanGoBack { get; }
  void GoBack();
  void NavigateToViewModel<TViewModel>(object parameter = null);
#if WINDOWS_PHONE
  void RemoveBackEntry();
#endif // WINDOWS_PHONE
}

我注入通過建構函式中,MainViewModel 的 INavigator 和 MainViewModel 使用 INavigator 調用 ViewFeed 方法中:

public void ViewFeed(FeedItem feedItem)
{
  this.
navigator.NavigateToViewModel<FeedItemViewModel>(feedItem);
}

當我看著 ViewFeed 與 INavigator 的對話模式時,我看到我想要驗證寫單元測試的兩件事:

  1. 傳遞到 ViewFeed FeedItem 是相同的 FeedItem 傳遞到 NavigateToViewModel。
  2. 傳遞給 NavigateToViewModel 的視圖模型類型是 FeedItemViewModel。

我其實寫測試之前,我需要創建另一個類比,這次為 INavigator。 圖 3 顯示為 INavigator 類比。 我跟隨了相同的圖案在之前與每個方法的委託作為一種方式時要執行的測試代碼獲取調用的實際方法。 再次,如果你正在具有嘲諷框架支援的平臺上,你不需要創建您自己的類比。

圖 3 類比為 INavigator 的

public class NavigatorMock : INavigator
{
  public bool CanGoBack { get; set; }
  public Action GoBackDelegate { get; set; }
  public void GoBack()
  {
    if (this.GoBackDelegate != null)
    {
      this.GoBackDelegate();
    }
  }
  public Action<Type, object> NavigateToViewModelDelegate { get; set; }
  public void NavigateToViewModel<TViewModel>(object parameter = null)
  {
    if (this.NavigateToViewModelDelegate != null)
    {
      this.NavigateToViewModelDelegate(typeof(TViewModel), parameter);
    }
  }
#if WINDOWS_PHONE
  public Action RemoveBackEntryDelegate { get; set; }
  public void RemoveBackEntry()
  {
    if (this.RemoveBackEntryDelegate != null)
    {
      this.RemoveBackEntryDelegate();
    }
  }
#endif // WINDOWS_PHONE
}

同在的地方我類比導航器類,我能把它使用在單元測試中,如中所示圖 4

圖 4 測試使用類比導航器導航

[TestMethod]
public void ViewFeed()
{
  // Arrange
  var viewModel = this.GetViewModel();
  var expectedFeedItem = new FeedItem();
  Type actualViewModelType = null;
  FeedItem actualFeedItem = null;
  this.Navigator.NavigateToViewModelDelegate = (viewModelType, parameter) =>
    {
      actualViewModelType = viewModelType;
      actualFeedItem = parameter as FeedItem;
    };
  // Act
  viewModel.ViewFeed(expectedFeedItem);
  // Assert
  Assert.AreSame(expectedFeedItem, actualFeedItem, "FeedItem");
  Assert.AreEqual(typeof(FeedItemViewModel), 
    actualViewModelType, "ViewModel Type");
}

此測試真正關心的是通過在附近的 FeedItem 是正確的要導航到的視圖模型是正確。 時使用的類比考試,很重要,要牢記什麼你應該關心為特定的測試和你不關心。 對於此測試,因為我有的 INavigator 介面的 MainViewModel 工作反對,我不需要在乎是否導航其實發生。 這是由無論實現 INavigator 的運行時實例處理。 我只被需要關心 INavigator 給予適當的參數,當發生導航時。

可測試中學瓷磚

最後,我要去看看測試面積中等的瓷磚。 可在 Windows 8 和 Windows Phone 8 中學瓷磚,他們讓使用者 pin 元素的一個應用程式向其主畫面,創建深層連結到應用程式的特定部分。 然而,輔助瓷磚的處理方式完全不同在兩個平臺,這意味著我必須提供特定于平臺的實現。 儘管不同,但我能夠提供一致的介面,我可以在這兩個平臺使用的輔助瓷磚的:

public interface ISecondaryPinner
{
  Task<bool> Pin(TileInfo tileInfo);
  Task<bool> Unpin(TileInfo tileInfo);
  bool IsPinned(string tileId);
}

TileInfo 類是 DTO 與這兩個平臺組合,創建輔助的圖塊的屬性。 因為每個平臺使用從 TileInfo 屬性的不同組合,每個平臺需要以不同的方式進行測試。 我會具體看 Windows 8 版本。 圖 5 顯示我的視圖模型如何使用 ISecondaryPinner。

有兩件事實際上發生了針法在圖 5。 第一是實際釘住中學平鋪。 第二個保存到本機存放區的 FeedItem。 所以,這是兩件事我需要測試。 因為此方法更改基於結果的嘗試別針 FeedItem 的視圖模型上的 IsFeedItemPinned 屬性,我還需要測試針法在 ISecondaryPinner 的兩個可能的結果:true 和 false。 圖 6 顯示的是第一次測試我執行的測試的成功方案。

圖 5 使用 ISecondaryPinner

public async Task Pin(Windows.UI.Xaml.FrameworkElement anchorElement)
{
  // Pin the feed item, then save it locally to make sure it's still available
  // when they return.
var tileInfo = new TileInfo(
    this.FormatSecondaryTileId(),
    this.FeedItem.Title,
    this.FeedItem.Title,
    Windows.UI.StartScreen.TileOptions.ShowNameOnLogo |
      Windows.UI.StartScreen.TileOptions.ShowNameOnWideLogo,
    new Uri("ms-appx:///Assets/Logo.png"),
    new Uri("ms-appx:///Assets/WideLogo.png"),
    anchorElement,
    Windows.UI.Popups.Placement.Above,
    this.FeedItem.Id.ToString());
  this.IsFeedItemPinned = await this.secondaryPinner.Pin(tileInfo);
  if (this.IsFeedItemPinned)
  {
    await SavePinnedFeedItem();
  }
}

圖 6 測試成功 pin

[TestMethod]
public async Task Pin_PinSucceeded()
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("http://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
    {
      actualPlacement = tileInfo.RequestPlacement;
      actualTileInfo = tileInfo;
      return true;
    };
  string actualKey = null;
  List<FeedItem> actualPinnedFeedItems = null;
  Storage.SaveAsyncDelegate = (key, value) =>
    {
      actualKey = key;
      actualPinnedFeedItems = (List<FeedItem>)value;
    };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.DisplayName, "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.ShortName, "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.AreEqual(Constants.PinnedFeedItemsKey, actualKey, "Save Key");
  Assert.IsNotNull(actualPinnedFeedItems, "Pinned Feed Items");
}

有一點更多的設置涉及與這比先前的測試。 第一,在控制器中之後, 我成立了一個 FeedItem 實例。 請注意我呼籲 ToString 的 Guid 的標題和作者。 那是因為我不關心實際的值是什麼,我只是照顧他們有我可以斷言一節中與比較的值。 因為連結是一個 Uri,所以提供了一個我需要為此工作,有效的 Uri。 再次,實際 Uri 是什麼不重要,只是它的有效。 安裝程式的其餘部分涉及到確保捕獲用於固定和保存比較斷言一節中的相互作用。 確保此代碼的關鍵其實測試的成功方案是 PinDelegate 返回 true,表示成功。

圖 7 顯示很多相同的測試,但對於不成功的情況。 PinDelegate 返回 false 的事實是什麼可以確保測試側重于最不成功的情況。 在不成功的情況下,我還需要驗證斷言一節中不叫 SaveAsync。

圖 7 測試不成功的 Pin

[TestMethod]
public async Task Pin_PinNotSucceeded()s
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("http://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
  {
    actualPlacement = tileInfo.RequestPlacement;
    actualTileInfo = tileInfo;
    return false;
  };
  var wasSaveCalled = false;
  Storage.SaveAsyncDelegate = (key, value) =>
  {
    wasSaveCalled = true;
  };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.DisplayName,
    "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.ShortName,
    "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.IsFalse(wasSaveCalled, "Was Save Called");
}

編寫可測試應用程式是一項挑戰。 它是特別具有挑戰性,測試在涉及使用者交互的展示層。 預先知道你要編寫一個可測試的應用程式允許您在每一步都可測試性而作出的決定。 你還可以去的東西,將使您的應用程式不太可測試和找出解決這些問題的方法。

過去的三篇文章的過程中,我討論了專門為 Windows 8 和 Windows Phone 8 編寫與使用 MVVM 模式,可測試應用程式。 在第一篇文章,我看著寫作可測試 Windows 8 的應用程式,同時仍然由自己利用 Windows 8 具體功能不容易可測試。 第二條進化的討論,包括: 開發可測試的跨平臺應用程式與 Windows 8 和 Windows Phone 8。 這篇文章,我顯示方式測試的應用程式,這麼努力,使可測試放在第一位。

使用 MVVM 是一個廣泛的主題,與一些不同的解釋。 我很高興,已經能夠分享我解釋一個有趣的話題。 我找到很多價值在使用 MVVM,尤其是因為它涉及到可測試性。 我還發現探索可測試性刺激,非常有用,和我高興分享我對編寫可測試應用程式的方法。

Brent Edwards 是 Magenic,一個側重于 Microsoft 堆疊和移動應用程式開發的自訂應用程式開發公司的主要領導顧問。他也是創始人之一的雙城市 Windows 8 使用者組在明尼阿波利斯。可通过 brente@magenic.com 与他联系。

衷心感谢以下技术专家对本文的审阅:Jason Bock (Magenic)
Jason博克是實踐帶 Magenic (www.magenic.com)。 他也是在.NET 中的元程式設計的合著 (www.manning.com/ 茲)。 聯繫到他在 jasonbock.net 或在 twitter 上:@jasonbock