譯者注:必須要下源碼,作者文章中的代碼比較飄逸,會(huì)跳過(guò)部分聲明妓肢,你可能會(huì)發(fā)現(xiàn)很多類(lèi)沒(méi)有定義,要去源碼找來(lái)看苫纤。當(dāng)然碉钠,我會(huì)在本文中盡量補(bǔ)充完整,使得這個(gè)入門(mén)級(jí)別的文章更加“入門(mén)”
項(xiàng)目截屏
介紹
ASP.NET Boilerplate 是一個(gè)開(kāi)源框架它整合了所有上面提到的框架或類(lèi)庫(kù)來(lái)使你的開(kāi)發(fā)更容易卷拘。它提供了一個(gè)很好的開(kāi)發(fā)應(yīng)用的基礎(chǔ)喊废。它原生支持依賴注入,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)和分層體系結(jié)構(gòu)栗弟。這個(gè)簡(jiǎn)單的應(yīng)用同樣實(shí)現(xiàn)了 數(shù)據(jù)驗(yàn)證污筷,異常處理,本地化和響應(yīng)式設(shè)計(jì)乍赫。
在這篇文章中瓣蛀,我會(huì)來(lái)展示如何使用下面的工具來(lái)開(kāi)發(fā)一款 單頁(yè)應(yīng)用 Single-Page Web Application (SPA)
- ASP.NET MVC 和 ASP.NET Web API 作為網(wǎng)站框架
- Angularjs 作為 SPA 框架
- EntityFramework 作為對(duì)象關(guān)系映射 ORM (Object-Relational Mapping) 框架
- Castle Windsor 作為依賴注入框架
- Twitter Bootstrap 作為 HTML/CSS 框架
- Log4Net 做登錄(logging), AutoMapper 做對(duì)象映射 (object-to-object mapping).
- 最后 ASP.NET Boilerplate 作為模板和項(xiàng)目框架
通過(guò)ABP模板創(chuàng)建應(yīng)用
ABP整合并配置好了企業(yè)級(jí)網(wǎng)站開(kāi)發(fā)用最好的工具陆蟆,為我們節(jié)省了大量時(shí)間。
讓我們打開(kāi) aspnetboilerplate.com/Templates 來(lái)創(chuàng)建我們的應(yīng)用
譯者注惋增,現(xiàn)在ABP模板下面多出一個(gè) module zero 是用來(lái)創(chuàng)建權(quán)限以及用戶的叠殷,勾和不勾與本文沒(méi)關(guān)系。另外源碼中的項(xiàng)目結(jié)構(gòu)也和現(xiàn)在下載的模板有所出入诈皿,還多出NHibernate or Durandal溪猿。無(wú)視就好
點(diǎn)擊按鈕后輸入驗(yàn)證碼 并開(kāi)始下載。
不要急著關(guān)掉纫塌,你需要按照網(wǎng)頁(yè)提示去創(chuàng)建你的本地sqlserver數(shù)據(jù)庫(kù)
就是剛才你給項(xiàng)目起的名字诊县,當(dāng)然你也可以翻過(guò)來(lái)修改連接字符串來(lái)指定數(shù)據(jù)庫(kù)
用之前配置好的 VS2015打開(kāi)下載的項(xiàng)目,可能會(huì)出現(xiàn)如圖情況措左,點(diǎn)擊確認(rèn)依痊。具體配置方法
加載完成后,右鍵點(diǎn)擊項(xiàng)目怎披,然后還原nuget包
把web項(xiàng)目設(shè)置為啟動(dòng)項(xiàng)目胸嘁,然后點(diǎn)擊F5運(yùn)行就能看到網(wǎng)站歡迎頁(yè)面
創(chuàng)建實(shí)體
我會(huì)創(chuàng)建一個(gè)簡(jiǎn)單應(yīng)用來(lái)給一些人發(fā)一些任務(wù),所以我需要 Task 任務(wù) 和 Person 人 2個(gè)實(shí)體凉逛。(注:實(shí)體作為業(yè)務(wù)的一部分,創(chuàng)建在core領(lǐng)域?qū)硬为?dú)擁有文件夾,后續(xù)還要存放倉(cāng)庫(kù)接口和關(guān)聯(lián)文件,如圖)
Task 任務(wù)性宏,簡(jiǎn)單定義了一個(gè)描述,創(chuàng)建時(shí)間和任務(wù)的狀態(tài)状飞。同時(shí)也含有一個(gè) 被指派人的引用(注:有人發(fā)現(xiàn)了,沒(méi)有定義主鍵,因?yàn)樗麄兌祭^承自Entity 會(huì)默認(rèn)加一個(gè) int 類(lèi)型 的字段 叫 Id.這里 Task任務(wù)通過(guò)泛型修改了id類(lèi)型為 long)
(注:文件夾默認(rèn)要用復(fù)數(shù)來(lái)取名,單復(fù)數(shù)同型的單詞比如person,文件夾不要取一樣的名字,會(huì)導(dǎo)致命名空間和類(lèi)名重名,無(wú)法使用)
public class Task : Entity<long>
{
[ForeignKey("AssignedPersonId")]
public virtual Person AssignedPerson { get; set; }
public virtual int? AssignedPersonId { get; set; }
public virtual string Description { get; set; }
public virtual DateTime CreationTime { get; set; }
public virtual TaskState State { get; set; }
public Task()
{
CreationTime = DateTime.Now;
State = TaskState.Active;
}
}
Person 人,簡(jiǎn)單定義一下名字
public class Person : Entity
{
public virtual string Name { get; set; }
}
創(chuàng)建 DbContext
眾所周知,EF 需要DbContext來(lái)工作.我們需要先定義他,ABP模板已經(jīng)為我們創(chuàng)建了一個(gè)DbContext模板,我們只需要在里面添加 IDbSet 就行了
public class SimpleTaskSystemDbContext : AbpDbContext
{
public virtual IDbSet<Task> Tasks { get; set; }
public virtual IDbSet<Person> People { get; set; }
public SimpleTaskSystemDbContext()
: base("Default")
{
}
public SimpleTaskSystemDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
他使用 Default 連接字符串 , 配置在webconfig中
<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />
創(chuàng)建migration
我們使用EF的code first migrations來(lái)升級(jí)數(shù)據(jù)庫(kù).ABP模板已經(jīng)默認(rèn)打開(kāi)了migrations,并且有下面這個(gè)配置類(lèi)
internalinternal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context)
{
context.People.AddOrUpdate(
p => p.Name,
new Person {Name = "Isaac Asimov"},
new Person {Name = "Thomas More"},
new Person {Name = "George Orwell"},
new Person {Name = "Douglas Adams"}
);
}
}
在這個(gè)seed方法中 我添加了4個(gè)人 來(lái)初始化數(shù)據(jù),然后打開(kāi) 包控制臺(tái)Package Manager Console,選擇 EF項(xiàng)目,輸入下面的命令
Add-Migration "InitialCreate"
然后
Update-Database
注:可以用tab"自動(dòng)完成",比如輸入update 然后 tab
當(dāng)我們修改我們的數(shù)據(jù)實(shí)體時(shí),可以很輕松的通過(guò)migration來(lái)完成數(shù)據(jù)庫(kù)升級(jí),想要了解更多?看entity framework的文檔
定義倉(cāng)儲(chǔ)接口
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì) (DDD)中,倉(cāng)儲(chǔ)被用來(lái)實(shí)現(xiàn)具體的數(shù)據(jù)庫(kù)訪問(wèn)代碼 .ABP 為每一個(gè)數(shù)據(jù)實(shí)體創(chuàng)建了一個(gè)自動(dòng)化的通用倉(cāng)儲(chǔ)IRepository接口,IRepository 已經(jīng)定義了一些常用怎刪改查方法,如圖
注:當(dāng)你聲明的倉(cāng)儲(chǔ)繼承于IRepository,就會(huì)自動(dòng)擁有這些方法
如果需要,我們可以擴(kuò)展這些倉(cāng)儲(chǔ),我會(huì)繼承他來(lái)創(chuàng)建一個(gè)Task倉(cāng)儲(chǔ),由于我想要分離接口和實(shí)現(xiàn),所以我先在這里創(chuàng)建接口(注:添加在核心領(lǐng)域?qū)?Tasks文件夾中)
public interface ITaskRepository : IRepository<Task, long>
{
List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}
它繼承于通用的ABP IRepository.所以他可以使用所有IRepository中定義的方法,并且我們可以添加我們自己的方法 GetAllWithPeople(...).
不需要為person創(chuàng)建倉(cāng)儲(chǔ),因?yàn)槟J(rèn)方法就已經(jīng)夠用了.ABP提供了一種注入通用倉(cāng)儲(chǔ)的方法而不需要單獨(dú)去創(chuàng)建倉(cāng)儲(chǔ)類(lèi).我們會(huì)在后面的章節(jié)(創(chuàng)建應(yīng)用服務(wù))中看到
我會(huì)把倉(cāng)儲(chǔ)接口定義在核心層,因?yàn)樗穷I(lǐng)域/業(yè)務(wù)的一部分
實(shí)現(xiàn)倉(cāng)儲(chǔ)
我們需要實(shí)現(xiàn)上面定義過(guò)的 ITaskRepository 接口.我會(huì)在EF層實(shí)現(xiàn)倉(cāng)儲(chǔ),這樣 領(lǐng)域?qū)泳屯耆蛿?shù)據(jù)分離了
當(dāng)我們創(chuàng)建模板的時(shí)候,ABP會(huì)默認(rèn)創(chuàng)建一個(gè)倉(cāng)儲(chǔ)類(lèi) SimpleTaskSystemRepositoryBase.擁有這樣一個(gè)基礎(chǔ)類(lèi)是非常好的,我們可以在日后為我們的倉(cāng)儲(chǔ)添加自己想要的通用方法.下面是我實(shí)現(xiàn) TaskRepository 的代碼
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
public TaskRepository(IDbContextProvider<ABPSimpleTaskTestDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
{
//在倉(cāng)儲(chǔ)方法中,我們不需要去定義數(shù)據(jù)庫(kù)連接 DbContext 和數(shù)據(jù)事務(wù)
var query = GetAll(); //GetAll() 返回 IQueryable<T>, 我們可以通過(guò)它來(lái)查詢.
//var query = Context.Tasks.AsQueryable(); //或者, 我們可以直接使用 EF's DbContext .
//var query = Table.AsQueryable(); //再者: 我們可以直接使用 'Table' 屬性來(lái)代替'Context.Tasks', 他們完全相同.
//添加了一下where過(guò)濾...
if (assignedPersonId.HasValue)
{
query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
}
if (state.HasValue)
{
query = query.Where(task => task.State == state);
}
return query
.OrderByDescending(task => task.CreationTime)
.Include(task => task.AssignedPerson) //一起查詢被分配任務(wù)的人
.ToList();
}
}
(注:刪掉 using System.Threading.Tasks;)
TaskRepository 繼承于 SimpleTaskSystemRepositoryBase 并且實(shí)現(xiàn)了我們剛才定義的 ITaskRepository 接口
GetAllWithPeople 是我們定義的 可以添加條件并拿到關(guān)聯(lián)實(shí)體 的查詢?nèi)蝿?wù)的方法.另外, 我們可以自由的在倉(cāng)儲(chǔ)中使用 Context (EF的 DBContext) 和 數(shù)據(jù)庫(kù) . ABP會(huì)為我們管理數(shù)據(jù)庫(kù)連接,事務(wù),創(chuàng)建和回收DbContext. (從 文檔 了解更多)
創(chuàng)建應(yīng)用服務(wù)
應(yīng)用服務(wù) 通過(guò)提供 外觀樣式方法 從 領(lǐng)域?qū)?中分離出 演示層 .我會(huì)定義應(yīng)用服務(wù)在應(yīng)用程序集(Application)中.首先,我會(huì)定義一個(gè)task的應(yīng)用服務(wù)接口
注:原文在這里跳過(guò)了很重要的內(nèi)容,Dto的相關(guān)介紹,譯者這里靠自己的理解補(bǔ)上
和其他層一樣,為tasks創(chuàng)建一個(gè)文件夾.并且創(chuàng)建好Dtos文件夾 Dto簡(jiǎn)介
譯者目前無(wú)法完全表述清楚這些文件的意思,所以從源碼中復(fù)制一下吧,畢竟我們這次的目的是ABP入門(mén)
創(chuàng)建ITaskAppService 接口
public interface ITaskAppService : IApplicationService
{
GetTasksOutput GetTasks(GetTasksInput input);
void UpdateTask(UpdateTaskInput input);
void CreateTask(CreateTaskInput input);
}
ITaskAppService 繼承于 IApplicationService. 這樣, ASP.NET Boilerplate 可以自動(dòng)為這個(gè)類(lèi)提供一些特征 (比如依賴注入和數(shù)據(jù)驗(yàn)證). 現(xiàn)在,讓我們來(lái)實(shí)現(xiàn) ITaskAppService:
/// <summary>
/// 實(shí)現(xiàn) <see cref="ITaskAppService"/> 來(lái)執(zhí)行task相關(guān)的應(yīng)用服務(wù)功能
///
/// 繼承 <see cref="ApplicationService"/>.
/// <see cref="ApplicationService"/> 包括一些常用的應(yīng)用服務(wù) (比如登陸和本地化).
/// </summary>
public class TaskAppService : ApplicationService, ITaskAppService
{
//這些在構(gòu)造函數(shù)中的成員使用了構(gòu)造函數(shù)注入
private readonly ITaskRepository _taskRepository;
private readonly IRepository<Person> _personRepository;
/// <summary>
/// 在構(gòu)造函數(shù)中,我們可以獲得所需要的類(lèi)和接口
/// 他們被依賴注入系統(tǒng)自動(dòng)送到這里
/// </summary>
public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
{
_taskRepository = taskRepository;
_personRepository = personRepository;
}
public GetTasksOutput GetTasks(GetTasksInput input)
{
//調(diào)用特殊的倉(cāng)儲(chǔ)方法 GetAllWithPeople
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
//使用 AutoMapper 自動(dòng)轉(zhuǎn)換 List<Task> 到 List<TaskDto>.
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}
public void UpdateTask(UpdateTaskInput input)
{
//我們可以使用日志(Logger),它已經(jīng)在基礎(chǔ)類(lèi)(ApplicationService)里定義過(guò)了
Logger.Info("Updating a task for input: " + input);
//通過(guò)id獲取實(shí)體可以用倉(cāng)儲(chǔ)中標(biāo)準(zhǔn)的Get方法
var task = _taskRepository.Get(input.TaskId);
//從接收到的task實(shí)體來(lái)修改屬性
if (input.State.HasValue)
{
task.State = input.State.Value;
}
if (input.AssignedPersonId.HasValue)
{
task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
}
//我們甚至不需要調(diào)用倉(cāng)儲(chǔ)中的update方法
//因?yàn)槊恳粋€(gè)應(yīng)用服務(wù)方法都是一個(gè)單元為默認(rèn)范圍的
//ABP自動(dòng)保存所有的修改,當(dāng)一個(gè)工作單元結(jié)束的時(shí)候(沒(méi)有任何例外)
}
public void CreateTask(CreateTaskInput input)
{
//我們可以使用日志(Logger),它已經(jīng)在基礎(chǔ)類(lèi)(ApplicationService)里定義過(guò)了
Logger.Info("Creating a task for input: " + input);
//用收到的input里的值新建一個(gè)task
var task = new Task { Description = input.Description };
if (input.AssignedPersonId.HasValue)
{
task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
}
//使用倉(cāng)儲(chǔ)中標(biāo)準(zhǔn)餓Insert方法來(lái)保存task
_taskRepository.Insert(task);
}
}
注:這里用到了automapper,而這個(gè)東西是需要配置的,到源文件里去找到下面2個(gè)文件,進(jìn)行配置
static class DtoMappings
{
public static void Map(IMapperConfigurationExpression mapper)
{
mapper.CreateMap<Task, TaskDto>();
}
}
[DependsOn(typeof(ABPSimpleTaskTestCoreModule), typeof(AbpAutoMapperModule))]
public class ABPSimpleTaskTestApplicationModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
//為了使用automapper我們必須在這里先聲明
Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper =>
{
DtoMappings.Map(mapper);
});
}
}
TaskAppService 使用倉(cāng)儲(chǔ)來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作. 它通過(guò) 構(gòu)造函數(shù)注入(constructor injection) 的方式,在他的構(gòu)造函數(shù)中提供了引用. ASP.NET Boilerplate 原生實(shí)現(xiàn)了依賴注入, 所以我們可以自由使用構(gòu)造注入或者屬性注入 (了解更多 ASP.NET Boilerplate 中的依賴注入 文檔).
注意毫胜, 我們通過(guò)注入IRepository<Person>來(lái)使用 PersonRepository. ASP.NET Boilerplate 就會(huì)自動(dòng)為我們創(chuàng)建倉(cāng)儲(chǔ). 如果 IRepository 中的默認(rèn)方法夠我們使用, 那我們就沒(méi)有必要去創(chuàng)建倉(cāng)儲(chǔ)類(lèi).
應(yīng)用服務(wù)方法需要用到數(shù)據(jù)傳輸對(duì)象 -- Data Transfer Objects (DTOs). 這是非常好的,而且我很建議使用這樣的模式. 但是你也不一定要照做诬辈,只要你能解決這些暴露實(shí)體到演示層的問(wèn)題
在 GetTasks 方法中, 我使用了之前實(shí)現(xiàn)過(guò)的 GetAllWithPeople 方法 酵使。 它返回了一個(gè) List<Task> ,但是我希望返回 List<TaskDto> 到演示層. AutoMapper 幫助我們自動(dòng)轉(zhuǎn)換 Task 對(duì)象到 TaskDto 對(duì)象. GetTasksInput 和 GetTasksOutput 是特別為 GetTasks 方法而定義的數(shù)據(jù)傳輸對(duì)象 (DTOs) .
在 UpdateTask 方法中, 我從數(shù)據(jù)庫(kù)獲得 Task (使用IRepository's 的 Get 方法) 并且修改了 Task的值. 注意焙糟,你沒(méi)有調(diào)用過(guò)倉(cāng)儲(chǔ)中的 Update 方法 . ASP.NET Boilerplate 實(shí)現(xiàn)了 UnitOfWork 模式. 所以, 所有在一個(gè)應(yīng)用服務(wù)里的修改就是一個(gè) unit of work (atomic原子的) 并且在方法的最后自動(dòng)提交到數(shù)據(jù)庫(kù).
在 CreateTask 方法,我簡(jiǎn)單地創(chuàng)建了一個(gè) Task 并且使用 IRepository自帶的 Insert 方法添加到數(shù)據(jù)庫(kù) .
ASP.NET Boilerplate 中的 ApplicationService 類(lèi) 有一些功能來(lái)幫助我們更容易地開(kāi)發(fā)應(yīng)用.比如, 它定義的 Logger 來(lái)做日志功能. 所以, 我們可以在這里讓 TaskAppService 繼承 ApplicationService 并且使用它的 Logger 日志功能. 我們?nèi)稳豢梢赃x擇性地去繼承這個(gè)類(lèi)口渔,只要你實(shí)現(xiàn) IApplicationService 接口(注意 ITaskAppService接口 繼承自 IApplicationService接口).
驗(yàn)證
ABP自動(dòng)在 application service 驗(yàn)證 inputs .CreateTask 方法獲取了 CreateTaskInput 作為參數(shù)
public class CreateTaskInput
{
public int? AssignedPersonId { get; set; }
[Required]
public string Description { get; set; }
}
這里 Description 被標(biāo)記成 [Required] 如果你想使用一些常規(guī)驗(yàn)證,你可以使用 Data Annotation attributes中所有的驗(yàn)證.你可以實(shí)現(xiàn)接口 ICustomValidate 就像我 在 UpdateTaskInput 中實(shí)現(xiàn)的一樣 :
public class UpdateTaskInput : ICustomValidate
{
[Range(1, long.MaxValue)]
public long TaskId { get; set; }
public int? AssignedPersonId { get; set; }
public TaskState? State { get; set; }
public void AddValidationErrors(List<ValidationResult> results)
{
if (AssignedPersonId == null && State == null)
{
results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" }));
}
}
public override string ToString()
{
return string.Format("[UpdateTask > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", TaskId, AssignedPersonId, State);
}
}
AddValidationErrors 方法 可以寫(xiě)你自己的驗(yàn)證方法代碼
異常處理
有人注意到了,我們還沒(méi)有處理過(guò)任何異常,ABP自動(dòng)為我們處理異常,記錄日志并給客戶端返回一個(gè)適當(dāng)?shù)腻e(cuò)誤.在客戶端可以看到錯(cuò)誤信息.事實(shí)上,我這個(gè)是為了ASP.NET MVC and Web API Controller actions 準(zhǔn)備的,由于我們會(huì)用WEBAPI來(lái)暴露 TaskAppService 所以我們不需要處理異常,可以看 exception handling 文檔 獲得更多信息
構(gòu)建web api services
我想要暴露我的服務(wù)到遠(yuǎn)程客戶端,這樣,我的 AngularJs 就可以使用ajax輕松調(diào)用他們.
ABP提供一個(gè)自動(dòng)化方式來(lái) 把 服務(wù)方法 轉(zhuǎn)換成webapi .我只要使用 DynamicApiControllerBuilder 就像下面:
DynamicApiControllerBuilder
.ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
.Build();
在這個(gè)例子中,ABP 在 Application 層的應(yīng)用程序中 找到了所有繼承 IApplicationService 的 接口 ,并且為每一個(gè) application service class 創(chuàng)建了一個(gè) web api controller.There are alternative syntaxes for fine control. 我們接下來(lái)會(huì)看到如何使用ajax來(lái)調(diào)用這些服務(wù)
開(kāi)發(fā) SPA
我會(huì)實(shí)現(xiàn)一個(gè)SPA 來(lái)作為我項(xiàng)目中的用戶界面 ,Angularjs 是使用最廣的 SPA框架
ABP提供了一個(gè)模板 可以很輕松地使用 AngularJs . 這個(gè)模板有2個(gè)頁(yè)面 Home 和 About .Bootstrap 作為 HTML/CSS 框架 同事本地化加入了 英語(yǔ)和 土耳其語(yǔ) 在 ABP的本地化系統(tǒng)中 (你可以很輕松地添加一個(gè)新語(yǔ)言或者移除它)
我們首先來(lái)修改路由,ABP使用 AngularUI-Router the de-facto standard router of AngularJs ,It provides state based routing modal,我們有2個(gè) 視圖 task list 和 new task ,所以我們修改 app.js 中的路由設(shè)置 如下:
app.config([
'$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('tasklist', {
url: '/',
templateUrl: '/App/Main/views/task/list.cshtml',
menu: 'TaskList' //Matches to name of 'TaskList' menu in SimpleTaskSystemNavigationProvider
})
.state('newtask', {
url: '/new',
templateUrl: '/App/Main/views/task/new.cshtml',
menu: 'NewTask' //Matches to name of 'NewTask' menu in SimpleTaskSystemNavigationProvider
});
}
]);
app.js 是主要的 js文件 用來(lái)啟動(dòng)和設(shè)置我們的SPA,需要注意的是 我們正在使用cshtml 文件來(lái)作為視圖,通常情況下,html文件會(huì)被作為AngularJs的視圖 .ABP經(jīng)過(guò)處理 使得cshtml也能使用AngularJs.這樣 我們就能使用razor來(lái)構(gòu)成我們的html了
ABP基礎(chǔ)插件 來(lái)創(chuàng)建和顯示菜單,他允許我們?cè)贑#中定義菜單但可以同時(shí)在C#和JS中使用,看 **SimpleTaskSystemNavigationProvider ** 類(lèi) 它創(chuàng)建了菜單 ,看 header.js/header.cshtml 通過(guò)angular 來(lái)顯示菜單
接下來(lái) 首先我要 為了 task list 視圖 創(chuàng)建一個(gè) Angular controller :
(function() {
var app = angular.module('app');
var controllerId = 'sts.views.task.list';
app.controller(controllerId, [
'$scope', 'abp.services.tasksystem.task',
function($scope, taskService) {
var vm = this;
vm.localize = abp.localization.getSource('SimpleTaskSystem');
vm.tasks = [];
$scope.selectedTaskState = 0;
$scope.$watch('selectedTaskState', function(value) {
vm.refreshTasks(); //當(dāng)selectedTaskState改變時(shí)調(diào)用
});
vm.refreshTasks = function() {
abp.ui.setBusy( //設(shè)置頁(yè)面為忙碌 直到 getTasks 方法完成
null,
taskService.getTasks({ //直接從 javascript 調(diào)用 application service 方法
state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null
}).success(function(data) {
vm.tasks = data.tasks;
})
);
};
vm.changeTaskState = function(task) {
var newState;
if (task.state == 1) {
newState = 2; //Completed
} else {
newState = 1; //Active
}
taskService.updateTask({
taskId: task.id,
state: newState
}).success(function() {
task.state = newState;
abp.notify.info(vm.localize('TaskUpdatedMessage'));
});
};
vm.getTaskCountText = function() {
return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length);
};
}
]);
})();
我用 'sts.views.task.list' 為控制器命名 這個(gè)是我的習(xí)慣,你也可以簡(jiǎn)單地起名叫 'ListController' ,AngularJs同樣使用依賴注入,我們?cè)谶@里注入了 $scope 和 abp.services.tasksystem.task . 前者是Angular的 局部變量,后者是一個(gè)自動(dòng)創(chuàng)建的 ITaskAppService 的js服務(wù)代理(我們之前在 構(gòu)建web api services 中設(shè)置過(guò))
ABP提供了一些基礎(chǔ)插件讓 服務(wù)器和客戶端 來(lái)使用 統(tǒng)一的 localization 本地化文本
vm.tasks 是一個(gè) tasks的集合 會(huì)被現(xiàn)實(shí)到視圖中 vm.refreshTasks 方法 會(huì)調(diào)用 taskService 來(lái)填充這個(gè)集合,他會(huì)在 selectedTaskState改變時(shí)被調(diào)用 (用 $scope.$watch 來(lái)監(jiān)聽(tīng) )
正如你所見(jiàn),調(diào)用一個(gè)服務(wù)器方法 是非常容易且直接了當(dāng)?shù)?. 這就是ABP的特色,他生成 web api 層 和 與之通行的 js 代理層 ,這樣 我們就能夠像調(diào)用js方法一樣調(diào)用服務(wù)端方法了.而且它完全集成在 AngularJs 中 (使用了 Angular's $http service)
讓我們接著來(lái)看task list 視圖
<div class="panel panel-default" ng-controller="sts.views.task.list as vm">
<div class="panel-heading" style="position: relative;">
<div class="row">
<!-- Title -->
<h3 class="panel-title col-xs-6">
@L("TaskList") - <span>{{vm.getTaskCountText()}}</span>
</h3>
<!-- Task state combobox -->
<div class="col-xs-6 text-right">
<select ng-model="selectedTaskState">
<option value="0">@L("AllTasks")</option>
<option value="1">@L("ActiveTasks")</option>
<option value="2">@L("CompletedTasks")</option>
</select>
</div>
</div>
</div>
<!-- Task list -->
<ul class="list-group" ng-repeat="task in vm.tasks">
<div class="list-group-item">
<span class="task-state-icon glyphicon" ng-click="vm.changeTaskState(task)" ng-class="{'glyphicon-minus': task.state == 1, 'glyphicon-ok': task.state == 2}"></span>
<span ng-class="{'task-description-active': task.state == 1, 'task-description-completed': task.state == 2 }">{{task.description}}</span>
<br />
<span ng-show="task.assignedPersonId > 0">
<span class="task-assignedto">{{task.assignedPersonName}}</span>
</span>
<span class="task-creationtime">{{task.creationTime}}</span>
</div>
</ul>
</div>
第一行的 ng-controller 屬性 綁定了 視圖的控制器 @L("TaskList") 為 "task list" 提供了本地化的文字翻譯 (在服務(wù)端生成html時(shí)),這全歸功于它是一個(gè)cshtml文件
ng-model綁定了 下拉框的變量,當(dāng)變量改變的時(shí)候 下拉框自動(dòng)改變 同樣,當(dāng)下拉框改變的時(shí)候 變量也會(huì)被保存,這個(gè)是 AngularJs 實(shí)現(xiàn)的雙向綁定
ng-repeat
是另外一個(gè) Angular 的指令 用來(lái)循環(huán)集合 生成html ,當(dāng) 集合改變的時(shí)候 (比如添加了一個(gè)內(nèi)容) 它會(huì)自動(dòng) 反射到視圖上,這個(gè)是AngularJs另外一個(gè)強(qiáng)大的功能
注意 當(dāng)你添加一個(gè) js文件 ,你需要添加它到你的頁(yè)面中
本地化
ABP提供了一個(gè)靈活且強(qiáng)大的 本地化系統(tǒng) 你可以使用xml文件 或者 Resource 文件 來(lái)作為 數(shù)據(jù)源 你可以定制 本地化數(shù)據(jù)源 看 documentation 獲得更多,我使用XML文件(在web項(xiàng)目中 的 Localization 文件夾)