上周我發布了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打開解決方案文件。







