ABP SimpleTaskSystem 翻譯

原帖

下載源碼

用我的方法配置好你的環(huán)境

譯者注:必須要下源碼,作者文章中的代碼比較飄逸,會(huì)跳過(guò)部分聲明妓肢,你可能會(huì)發(fā)現(xiàn)很多類(lèi)沒(méi)有定義,要去源碼找來(lái)看苫纤。當(dāng)然碉钠,我會(huì)在本文中盡量補(bǔ)充完整,使得這個(gè)入門(mén)級(jí)別的文章更加“入門(mén)”

項(xiàng)目截屏

Paste_Image.png

介紹


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 MVCASP.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ù)

Paste_Image.png

就是剛才你給項(xiàng)目起的名字诊县,當(dāng)然你也可以翻過(guò)來(lái)修改連接字符串來(lái)指定數(shù)據(jù)庫(kù)

用之前配置好的 VS2015打開(kāi)下載的項(xiàng)目,可能會(huì)出現(xiàn)如圖情況措左,點(diǎn)擊確認(rèn)依痊。具體配置方法

Paste_Image.png

加載完成后,右鍵點(diǎn)擊項(xiàng)目怎披,然后還原nuget包

把web項(xiàng)目設(shè)置為啟動(dòng)項(xiàng)目胸嘁,然后點(diǎn)擊F5運(yùn)行就能看到網(wǎng)站歡迎頁(yè)面

Paste_Image.png

創(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)文件,如圖)

Paste_Image.png

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)目,輸入下面的命令

Paste_Image.png
Add-Migration "InitialCreate"

然后

Update-Database

注:可以用tab"自動(dòng)完成",比如輸入update 然后 tab

Paste_Image.png

當(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)定義了一些常用怎刪改查方法,如圖

Paste_Image.png

注:當(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)介

Paste_Image.png

譯者目前無(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)行配置

Paste_Image.png
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

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 文件夾)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市穿撮,隨后出現(xiàn)的幾起案子缺脉,更是在濱河造成了極大的恐慌,老刑警劉巖悦穿,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攻礼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡咧党,警方通過(guò)查閱死者的電腦和手機(jī)秘蛔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)陨亡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傍衡,“玉大人深员,你說(shuō)我怎么就攤上這事⊥芄。” “怎么了倦畅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绣的。 經(jīng)常有香客問(wèn)我叠赐,道長(zhǎng),這世上最難降的妖魔是什么屡江? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任芭概,我火速辦了婚禮,結(jié)果婚禮上惩嘉,老公的妹妹穿的比我還像新娘罢洲。我一直安慰自己,他們只是感情好文黎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布惹苗。 她就那樣靜靜地躺著,像睡著了一般耸峭。 火紅的嫁衣襯著肌膚如雪桩蓉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天劳闹,我揣著相機(jī)與錄音院究,去河邊找鬼。 笑死本涕,一個(gè)胖子當(dāng)著我的面吹牛儡首,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偏友,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蔬胯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了位他?” 一聲冷哼從身側(cè)響起氛濒,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹅髓,沒(méi)想到半個(gè)月后舞竿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窿冯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年骗奖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡执桌,死狀恐怖鄙皇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仰挣,我是刑警寧澤伴逸,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站膘壶,受9級(jí)特大地震影響错蝴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颓芭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一顷锰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亡问,春花似錦馍惹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至慎框,卻和暖如春良狈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笨枯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工薪丁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人馅精。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓严嗜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親洲敢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漫玄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容