上周我發布了Apworks框架的最新版本,打算寫點文章來介紹如何在實際項目中使用Apworks進行開發。今天先簡單寫一點東西,首先說明一下,Apworks本身是面向領域驅動的一套開發框架,因此,領域模型的分析和建立就是使用Apworks的重點;然而,在大家對Apworks還沒有任何了解的基礎上,我暫時先拋開領域模型的建立,先向大家展示一下,如何使用Apworks開發第一個可以運行的程序。在這篇文章的介紹中,我們的領域模型只有一個聚合:日記(Note),而且我們會將這個聚合對象同時用作數據傳輸對象,這當然與DDD的宗旨是違背的,但為了簡化介紹過程,我們也不把問題復雜化了。現在,就讓我們一起來創建一個對“日記”信息進行維護的ASP.NET MVC Web API應用程序吧。
通過本文的介紹,你將了解到:
- Apworks下領域模型的建立
- Apworks框架的初始化
- 使用基於Entity Framework的倉儲實現聚合維護
新建ASP.NET MVC Web API項目
首先,新建一個ASP.NET MVC Web API項目,這個過程很簡單,打開Visual Studio,然后新建一個ASP.NET MVC 4 Web Application,取名為NoteService(姑且取這個名字吧),然后在New ASP.NET MVC 4 Project對話框中,選擇Web API模板,然后直接單擊OK按鈕:
在完成解決方案的創建以后,在Solution Explorer(解決方案資源管理器)中,可以看到標准的Web API目錄結構:
創建領域模型
再次聲明,雖然接下來的步驟會在NoteService的Models目錄下新建領域模型,但Models目錄本身是用來定義View Model的,作為領域模型,定義在另一個單獨的程序集中會更合適。總之,需要對領域模型(Domain Model)和視圖模型(View Model)進行區分。
首先,在NoteService項目上單擊右鍵,選擇Manage NuGet Packages菜單,在彈出的對話框中,搜索Apworks,然后選擇Apworks,單擊Install按鈕:
接下來,在項目的Models目錄上點右鍵,選擇“Add –> Class”菜單,創建一個類,將類保存成Note.cs文件,並在文件中輸入以下代碼:
using Apworks; using System; namespace NoteService.Models { public enum Weather { Cloudy, Rainy, Sunny, Windy } public class Note : IAggregateRoot { public Guid ID { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime CreatedDate { get; set; } public Weather Weather { get; set; } } }
行了,目前我們就只創建一個聚合:Note。從上面的代碼得知,聚合的根需要繼承於IAggregateRoot接口。
使用Unity作為IoC容器
Apworks目前僅集成了Unity作為整個框架的IoC容器,因此,我們需要為Unity的使用作准備。同樣,打開Manage NuGet Packages對話框,從中選擇Apworks Unity Object Container,然后點擊Install安裝。注意:此時NuGet會把所依賴的Unity也一並安裝:
另外,我們還需要添加對Unity.WebAPI組件的引用,該組件提供了Unity對WebAPI的集成,以便能夠在WebAPI中更好地使用Unity。用同樣的方法添加引用:
OK,使用Unity的准備工作已經完成了,接下來,我們對Apworks進行配置。
配置Apworks框架
Apworks框架提供三種配置方式:app/web.config、直接寫代碼配置(使用RegluarConfigSource類),以及Fluent Interface。為應用框架提供多樣化的配置方式,這是框架架構中必不可少的工作,究其原因和實現方式,請參考我以前寫的一篇文章:《.NET應用框架架構設計實踐 - 為應用程序框架提供多樣化的配置方式》。Fluent Interface這種配置方式的實現,請參考我前面寫的文章:《在C#中使用裝飾器模式和擴展方法實現Fluent Interface》。下面言歸正傳。
打開Global.asax.cs文件,首先添加對Apworks.Application、Apworks.Config.Fluent、Microsoft.Practices.Unity和Unity.WebApi這幾個命名空間的引用,然后在Application_Start中添加以下代碼:
AppRuntime.Instance .ConfigureApworks() .UsingUnityContainerWithDefaultSettings() .Create((sender, e) => { var container = e.ObjectContainer.GetWrappedContainer<UnityContainer>(); // TODO: register types GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container); }).Start();
上面代碼應該非常清晰地表述了配置內容,我也就不多解釋了,在應用中可以使用IntelliSense來了解一下Apworks Fluent Interface還實現了哪些配置項目。
使用基於Entity Framework的倉儲
以同樣的方法,引入Apworks Entity Framework Repository組件:
在NoteService項目上新建一個類,取名為NoteServiceDbContext,該類實現如下:
public class NoteServiceDbContext : DbContext { public NoteServiceDbContext() : base("NoteServiceDB") { } public DbSet<Note> Notes { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Note>() .HasKey(p => p.ID) .Property(p => p.ID) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); base.OnModelCreating(modelBuilder); } }
另外,為了能在第一次創建數據庫時,加入一些樣本數據作為測試,我還添加了以下類:
public class NoteServiceInitializer : DropCreateDatabaseIfModelChanges<NoteServiceDbContext> { protected override void Seed(NoteServiceDbContext context) { new List<Note> { new Note { Title = "My first note", Content = "This is my first note.", CreatedDate = DateTime.Now, Weather = Weather.Sunny }, new Note { Title = "My second note", Content = "This is my second note.", CreatedDate = DateTime.Now, Weather = Weather.Windy } }.ForEach(p => context.Notes.Add(p)); } }
好了,Entity Framework的准備已經做好,接下來就是幾個常規的配置項。
首先,修改web.config文件,將Entity Framework的數據庫連接配置加上:
<entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="Data Source=(LocalDb)\v11.0; Initial Catalog=NoetServiceDB; Integrated Security=True; Connect Timeout=120; MultipleActiveResultSets=True; AttachDBFilename=|DataDirectory|\NoteServiceDB.mdf" /> </parameters> </defaultConnectionFactory> </entityFramework>
然后,打開Global.asax.cs文件,將Application_Start方法修改成:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // Initialize database Database.SetInitializer<NoteServiceDbContext>(new NoteServiceInitializer()); AppRuntime.Instance .ConfigureApworks() .UsingUnityContainerWithDefaultSettings() .Create((sender, e) => { var container = e.ObjectContainer.GetWrappedContainer<UnityContainer>(); // TODO: register types container.RegisterInstance<NoteServiceDbContext>(new NoteServiceDbContext(), new PerResolveLifetimeManager()) .RegisterType<IRepositoryContext, EntityFrameworkRepositoryContext>(new HierarchicalLifetimeManager(), new InjectionConstructor(new ResolvedParameter<NoteServiceDbContext>())) .RegisterType(typeof(IRepository<>), typeof(EntityFrameworkRepository<>)); GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container); }).Start(); }
這部分代碼中,改動的地方是:1、使用Database.SetInitializer方法,設置EF的數據庫初始化策略,我們使用已經編寫好的NoteServiceInitializer作為初始化策略;2、在Create的委托方法中,我們添加了對IRepositoryContext、IRepository<>以及NoteServiceDbContext的類型注冊,以使用Entity Framework Repository。注意:IRepositoryContext被注冊為HierarchicalLifetimeManager生命周期,以便Unity能夠在Request結束時能夠正確調用IRepositoryContext的Dispose方法。詳細請參見:http://devtrends.co.uk/blog/introducing-the-unity.webapi-nuget-package。
開發Web API服務
在Solution Explorer(解決方案資源管理器)中,將ValuesController改名為NotesController,然后,改寫NotesController類如下:
using Apworks.Repositories; using NoteService.Models; using System; using System.Collections.Generic; using System.Web.Http; namespace NoteService.Controllers { public class NotesController : ApiController { readonly IRepository<Note> noteRepository; public NotesController(IRepository<Note> noteRepository) { this.noteRepository = noteRepository; } // GET api/notes public IEnumerable<Note> Get() { return noteRepository.FindAll(); } // GET api/notes/6EE246A5-9E68-4BC9-BA24-7F4EC2B326D4 public Note Get(Guid id) { return noteRepository.GetByKey(id); } // POST api/notes public void Post([FromBody]Note value) { noteRepository.Add(value); noteRepository.Context.Commit(); } // PUT api/notes/6EE246A5-9E68-4BC9-BA24-7F4EC2B326D4 public void Put(Guid id, [FromBody]Note value) { var note = noteRepository.GetByKey(id); note.Title = value.Title; note.Content = value.Content; note.CreatedDate = value.CreatedDate; note.Weather = value.Weather; noteRepository.Update(note); noteRepository.Context.Commit(); } // DELETE api/notes/6EE246A5-9E68-4BC9-BA24-7F4EC2B326D4 public void Delete(Guid id) { var note = noteRepository.GetByKey(id); noteRepository.Remove(note); noteRepository.Context.Commit(); } } }
OK,至此,一個使用Apworks開發的ASP.NET MVC Web API服務已經完成,由於DependencyResolver的使用,NotesController在被創建時會獲得IRepository<Note>的實例(由IoC通過構造函數注入),於是,在每個方法調用中,都能使用Note倉儲完成所需的操作。
測試
我們可以使用Microsoft ASP.NET Web API Client Libraries對開發的Web API服務進行測試,具體用法也就不說了,可以自行參閱網上的文章。例如:可以用以下方法測試GET請求:
[TestClass] public class NoteServiceTest { static readonly HttpClient client = new HttpClient(); [ClassInitialize] public static void TestInitialize(TestContext context) { client.BaseAddress = new Uri("http://localhost:10895"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } [TestMethod] public void GetTest() { var response = client.GetAsync("api/notes").Result; Assert.IsTrue(response.IsSuccessStatusCode); var notes = response.Content.ReadAsAsync<IEnumerable<Note>>().Result; Assert.IsTrue(notes.Count() > 0); } }
限於篇幅,其它方法的測試用例我就不貼代碼了,我是通過了所有測試的:
總結
本文介紹了在ASP.NET MVC Web API中使用Apworks框架開發一個簡單的HTTP服務的一般步驟。通過本文的介紹,我們可以了解到如何基於Apworks創建我們的領域模型,如何配置Apworks以使用Unity Container,如何配置Apworks以使用基於Entity Framework的倉儲。同時我們還了解到了一些ASP.NET MVC Web API的開發技術,希望本文對打算使用Apworks進行面向領域驅動架構開發的開發人員有一定的幫助。
或許在讀完本文之后,你會覺得,在本案例中,直接使用Entity Framework貌似要比使用Apworks來得更快。不錯,本案例僅僅是對Apworks的一個開場演示,它的重點是在Apworks框架的使用上,而不是在Entity Framework上。主要是時間有限,我目前沒有辦法再去使用Apworks重新做一套完整的案例(事實上我已經向社區貢獻了一個案例:Byteart Retail,不過它並沒有使用Apworks的任何組件),但我有可能會在近期將Byteart Retail改成使用Apworks實現,相信到時候你會發現:使用Apworks開發面向領域驅動分層架構的應用程序,真的非常簡單。我也會不定期地繼續發布一些有關Apworks框架應用的文章,來幫助大家更好地理解這個框架。
本文案例代碼
請【單擊此處】下載本文案例代碼,下載解壓后請用Visual Studio 2012 Update 2打開解決方案文件。