ABP示例程序-使用AngularJs,ASP.NET MVC,Web API和EntityFramework建立N層的單頁面Web應用

  本片文章翻譯自ABP在CodeProject上的一個簡單示例程序,網站上的程序是用ABP以前的版本建立的,模板建立界面及工程文檔有所改變,本文基於最新的模板建立。經過這個簡單的示例能夠對ABP有個更深刻的瞭解,每一個工程裏應該寫什麼樣的代碼,代碼如何組織以及ABP是如何在工程中發揮做用的。javascript

源文檔地址:https://www.codeproject.com/Articles/791740/Using-AngularJs-ASP-NET-MVC-Web-API-and-EntityFramhtml

源碼能夠下載文檔中的示例代碼,也能夠下載我使用最新模板建立的示例工程,github地址:https://github.com/YSmileX/SimpleTaskSystem0726java

 

使用AngularJs,ASP.NET MVC,Web API和EntityFramework建立N層的單頁面Web應用git

介紹github

   在本文中,將展現給你如何使用下面的工具從頭至尾發佈一個單頁面Web應用(SPA):web

  • ASP.NET MVCASP.NET Web API做爲Web框架。
  • Angularjs做爲SPA框架
  • EntityFramework做爲ORM(Object-Relational Mapping)框架。
  • Castle Windsor做爲依賴注入框架。
  • Twitter Bootstrap做爲HTML/CSS框架。
  • 日誌使用Log4Net,對象到對象映射使用AutoMapper
  • ASP.NET Boilerplate做爲啓動模板和應用框架。

  ABP是一個開源的應用框架,它結合了這些全部的框架和類庫能夠很容易的發佈你的應用。它使用最佳實踐提供給咱們一個基礎設施來發布應用。它天生支持依賴注入領域驅動分層架構。示例應用還實現了校驗異常處理本地化響應式設計數據庫

從模板建立應用api

   ABP提供了模板來節省咱們建立一個新應用的時間,模板中包含並配置了最好的工具來構建企業級別的Web引用。數組

  讓咱們到aspnetboilerplate.com/Templates來從模板構建咱們的應用:架構

  這裏咱們選擇ASP.NET MVC 5.X標籤頁,而後選擇SPA(Sigle Page Application) with AngularJs,ORM選擇EntityFramework。工程名稱中輸入SimpleTaskSystem。點擊「Create my project!」按鈕就會建立並下載咱們的解決方案。

  在解決方案中包含5個工程。Core工程爲領域(業務)層,Application工程爲應用層,WebApi工程實現Web Api控制器,Web工程爲展現層,EntityFramework工程實現Entityframework。

  注意:若是你從本文中下載示例解決方案,解決方案中會有7個工程。我實現了NHibernate和Durandal的支持。若是你對NHibernate或Durandal不感興趣,能夠忽略這兩個工程。

建立實體

   我將建立一個簡單的應用,這個應用能夠建立tasks並把這些tasks分配給people。因此我須要TaskPerson實體。

  Task實體簡單定義了Description,CreationTime和State。它還有一個對Person(AssignedPerson)的可選引用:

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實體更簡單,僅定義了person的Name:

public class Person : Entity
{
    public virtual string Name { get; set; }
}

  ABP提供了Entity類,它定義了Id屬性。我從這個實體類派生實體。由於我從Entity<long>派生,因此Task類有一個long類型的Id。Person類有一個int類型的Id。由於int爲默認的主鍵類型,我沒有指定它。

  我在Core工程中定義實體,由於實體爲領域/業務層的一部分。

建立DbContext

   如你所知,EntityFramework須要DbContext類。咱們首先定義它。ABP模板爲咱們建立了一個DbContext。我僅僅須要爲Task和Person添加IDbSets。這是個人DbContext類:

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)
    {
            
    }
}

  它使用web.config中的Default鏈接字符串。定義以下所示:

<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />

建立數據庫遷移

   咱們使用EntityFramework的Code First遷移來建立和維護數據庫模式。ABP模板默認啓用遷移並添加了一個Configuration類,以下所示:

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"}
            );
    }
}

  在Seed方法中,我添加了4個people做爲初始化數據。如今,將建立初始遷移。打開包管理控制檯並鍵入下面的命令:我建立的工程名稱爲SimpleTaskSystem0726,2017.7.26從ABP官網模板建立

   

  Add-Migration "InitalCreate"命令建立了一個名爲InitialCreate的類,以下所示:

    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.People",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.Id);
            
            CreateTable(
                "dbo.Tasks",
                c => new
                    {
                        Id = c.Long(nullable: false, identity: true),
                        AssignedPersonId = c.Int(),
                        Description = c.String(),
                        CreationTime = c.DateTime(nullable: false),
                        State = c.Byte(nullable: false),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.People", t => t.AssignedPersonId)
                .Index(t => t.AssignedPersonId);
            
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.Tasks", "AssignedPersonId", "dbo.People");
            DropIndex("dbo.Tasks", new[] { "AssignedPersonId" });
            DropTable("dbo.Tasks");
            DropTable("dbo.People");
        }
    }

  咱們建立了建立數據所須要的類,可是尚未建立數據庫。運行下面的指令建立數據庫:

PM> Update-Database

  這個命令會運行遷移,建立數據庫並建立初始數據:

  當咱們改變實體類時,可使用Add-Migration命令建立新的遷移類,Update-Database命令更新數據庫。要學習更多源於數據庫遷移的知識,參見framework的文檔。

定義倉儲

   在領域驅動設計中,倉儲用於實現特定數據庫的代碼。ABP使用泛型IRepository接口自動爲每個實體建立了一個倉儲。IRepository爲select,insert,update,delete還有其餘一些定義了共同的方法:

  咱們能夠基於需求擴展這些倉儲。我將擴展它來建立一個Task倉儲。我想接口與實現分離開,首先建立倉儲接口。這裏是Task倉儲接口:

public interface ITaskRepository : IRepository<Task, long>
{
    List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

  它擴展了ABP的泛型IRepository接口。因此,ITaskRepository默認繼承了全部這些方法的定義。它也能夠添加本身的方法,如我定義了GetAllWithPeople(...)

  沒有必要爲Person建立一個倉儲,由於默認的方法已經足夠使用。ABP提供了不用建立倉儲類注入泛型倉儲的方式。咱們將在「構建應用服務」部分的TaskAppService類中見到它。

  我在Core工程中定義了倉儲接口,由於它們是領域/業務層的一部分。

實現倉儲

   咱們應該實現上面定義的ITaskRepository接口。我在EntityFramework工程中實現倉儲。這樣,領域層徹底獨立於EntityFramework。

  當咱們建立工程模板時,ABP在咱們的工程中爲倉儲定義了一個泛型基類:SimpleTaskSystemRepositoryBase。建立這樣一個基類是好的實踐,這樣咱們能夠之後爲咱們的倉儲添加共同的方法。你能夠在代碼中看見這個類的定義。我派生它來實現TaskRepository

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
    public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    {
        //在倉儲方法中,咱們不須要處理建立/釋放DBConnections,DbContext和transactions,ABP會處理。
            
        var query = GetAll(); //GetAll() returns IQueryable<T>, 因此咱們基於它查詢.
        //var query = Context.Tasks.AsQueryable(); //咱們也能夠直接使用 EF's DbContext對象.
        //var query = Table.AsQueryable(); //另外一個選擇: 咱們能夠直接使用‘Table’屬性取代‘Context.Tasks’,他們是一致的
            
        //添加 Where 條件...

        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) //在同一個查詢裏包含assiged person
            .ToList();
    }
}

   TaskRepository派生自SimpleTaskSystemRepositoryBase並實現了咱們定義的ITaskRepository

  GetAllWithPeople是咱們特定的方法來獲取tasks,方法中包含AssignedPerson(預獲取)並能夠根據一些條件選擇性的過濾。咱們能夠在倉儲中自由使用Context(EF`s DBContext)對象和數據庫。ABP爲咱們管理數據庫鏈接,事務,建立並釋放DbContext(參見文檔瞭解更多信息)。

構建應用服務

   應用服務經過提供外觀方法來隔離展現層和領域層。我在工程的Application程序集中定義應用服務。首先,爲task應用服務定義接口:

public interface ITaskAppService : IApplicationService
{
    GetTasksOutput GetTasks(GetTasksInput input);
    void UpdateTask(UpdateTaskInput input);
    void CreateTask(CreateTaskInput input);
}

  ITaskAppService擴展了IApplicationService。這樣,ABP自動爲這個類提供一些特徵(如依賴注入和校驗)。如今,讓咱們實現ITaskAppService:

 public class TaskAppService : ApplicationService, ITaskAppService
    {
        //在構造函數中使用構造函數注入設置這些成員
        private readonly ITaskRepository _taskRepository;
        private readonly IRepository<Person> _personRepository;

        /// <summary>
        /// 在構造函數中,咱們能夠獲取須要的類/接口。他們自動被依賴注入系統初始化。
        /// </summary>
        /// <param name="taskRepository"></param>
        /// <param name="personRepository"></param>
        public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
        {
            _taskRepository = taskRepository;
            _personRepository = personRepository;
        }

        public GetTasksOutput GetTasks(GetTasksInput input)
        {
            //調用task倉儲的GetAllWithPeople方法
            var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);

            //使用AutoMapper自動將List<Task>轉換爲List<TaskDto>
            return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(tasks) };
        }

        public void UpdateTask(UpdateTaskInput input)
        {
            //咱們可使用Logger,它在應用服務基類中定義
            Logger.Info("Updating a task for input:" + input);

            //使用倉儲的標準方法Get經過給定的id從新獲取task實體
            var task = _taskRepository.Get(input.TaskId);

            //更新從新獲取的task實體的屬性
            if (input.State.HasValue)
            {
                task.State = input.State.Value;
            }

            if (input.AssignedPersonId.HasValue)
            {
                task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
            }

            //咱們不須要調用倉儲的Update方法。由於應用服務方法默認爲一個工做單元。
            //當工做單元結束時(沒有任何異常),ABP自動保存全部更改。
        }

        public void CreateTask(CreateTaskInput input)
        {
            //咱們可使用Logger,它在應用服務基類中定義
            Logger.Info("Creating a task for input:" + input);

            //使用給定的input的屬性建立一個新的Task
            var task = new Tasks.Task {Description = input.Description};

            if (input.AssignedPersonId.HasValue)
            {
                task.AssignedPersonId = input.AssignedPersonId.Value;
            }

            //使用倉儲標準Insert方法保存實體
            _taskRepository.Insert(task);
        }
    }

  TaskAppService使用倉儲來操做數據庫。它在構造函數中經過構造函數注入模式獲取引用。ABP天生實現了依賴注入,因此咱們能夠自由使用構造函數注入或屬性注入(參見ABP文檔中的依賴注入)。

  注意咱們經過注入IRepository<Person>來使用PersonRepository。ABP自動爲實體建立倉儲。若是IRepository默認的方法對咱們已足夠,咱們不須要再建立倉儲類。

  服務方法使用數據傳輸對象(DTOs)工做。這是一個最佳實踐,我建議使用這種模式。可是,若是你能夠處理在展現層暴露實體的問題就能夠不使用它。

  在GetTasks方法中,我使用了以前實現的GetAllWithPeople方法。它返回List<Task>可是我須要返回List<TaskDto>給展現層。AutoMapper幫助咱們自動轉換Task對象爲TaskDto對象。GetTasksInput和GetTasksOut是爲GetTasks方法定義的特定DTOs。

  在UpdateTask方法中,我從數據庫從新獲取Task(使用IRepository的Get方法)並更新Task的屬性。注意,我沒有調用倉儲的Update方法。ABP實現了工做單元模式。因此,應用服務方法中的全部更改成一個工做單元(原子的),在方法結束的時候自動應用到數據庫。

  在CreateTask方法中,我簡單建立了一個新的Task,使用IRepository的Insert方法插入到數據庫。

  ABP的ApplicationService類有一些屬性能夠簡化發佈應用服務。例如,它定義了Logger屬性歷來記錄日誌。因此,咱們從ApplicationService派生TaskAppService並使用它的Logger屬性。能夠選擇性的使用這個類可是必須實現IApplicationService(注意ITaskAppService擴展了IApplicationService)。

校驗

  ABP自動校驗應用服務方法的輸入。CreateTask方法使用CreateTaskInput做爲參數:

public class CreateTaskInput
{
    public int? AssignedPersonId { get; set; }

    [Required]
    public string Description { get; set; }
}

  這裏,Description標記爲Required。你可使用任何的數據標記特性。若是你想建立自定義校驗,能夠實現ICustomValidate接口:

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方法裏編寫自定義校驗代碼。

處理異常

  注意咱們不須要處理任何異常。ABP自動處理異常,記錄並返回一個恰當的錯誤信息到客戶端。在客戶端處理這些錯誤信息並顯示給用戶。實際上,這也適用於ASP.NET MVC和Web API控制器actions。由於咱們將使用Web API暴露TaskAppService,咱們不須要處理異常。參見異常處理文檔瞭解更多詳情。

構建Web API服務

   我想將個人應用服務暴露給遠程客戶端。這樣,個人AngularJs應用能夠很容易的使用AJAX調用這些服務方法。

  ABP提供了一種自動暴露應用服務做爲ASP.NET Web API的方法。我僅僅使用DynamiApicControllerBuilder,以下所示:

DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();

  對於這個示例,ABP在應用層程序集中查找全部集成IApplicationService的接口併爲每個應用服務方法建立一個Web api控制器。還有可選的語法能夠實現更好的控制。咱們將見到如何經過AJAX調用這些服務。

發佈SPA

  我將實現一個單頁面應用,做爲工程的用戶接口。AngularJs(Google出品)是使用最普遍的SPA框架之一。

  ABP提供了一個輕鬆使用AngularJs的模板。這個模板有兩個pages(Home和About),能夠平滑的在這兩個頁面之間切換。使用Twitter BootStrap做爲HTML/CSS框架(所以,它是響應式的)。它還使用ABP的本地化系統(你能夠簡單的添加其餘語言或移除其中一個)本地化爲English和Turkish。

  咱們首先更改模板的路由。ABP模板使用AngularUI-Router,AngularJs的de-facto標準路由。它基於路由模式提供狀態。咱們將有兩個視圖:task list和new task。因此,咱們將在app.js中改變路由定義,以下所示:

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是主要的javascript文件用來配置和啓動咱們的SPA。注意咱們可使用cshtml文件做爲視圖!一般,在AngularJs中使用html文件做爲視圖。ABP使的可使用cshtml文件。所以咱們可使用razor引擎生成HTML。

  ABP提供了一個基礎設施來建立和顯示菜單。它容許在C#中定義菜單,能夠同時在C#和javascript中使用。建立菜單參見SimpleTaskSystemNavigationProvider類,使用angular方式顯示菜單參見header.js/header.cshtml

  首先,爲task list視圖建立一個Angular控制器:

(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();
            });

            vm.refreshTasks = function() {
                abp.ui.setBusy( //直到getTasks完成以前設置整個頁忙碌
                    null,
                    taskService.getTasks({ //從javascript中直接調用服務方法
                        state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null
                    }).success(function(data) {//注意,若是angular版本高於1.4,success需改成then
                        vm.tasks = data.tasks;
                    })
                );
            };

            vm.changeTaskState = function(task) {
                var newState;
                if (task.state == 1) {
                    newState = 2; //完成
                } else {
                    newState = 1; //活動的
                }

                taskService.updateTask({
                    taskId: task.id,
                    state: newState
                }).success(function() {//注意,若是angular版本高於1.4,success需改成then
                    task.state = newState;
                    abp.notify.info(vm.localize('TaskUpdatedMessage'));
                });
            };

            vm.getTaskCountText = function() {
                return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length);
            };
        }
    ]);
})();

  控制器的名稱定義爲'sts.views.taks.list'。這是個人習慣(爲了代碼可擴展)可是你能夠簡化命名爲'ListController'。AngularJs也可使用依賴注入。這裏咱們注入了'$scope'和'abp.services.tasksystem.task'。第一個爲Angular的scope變量,第二個爲自動爲ITaskAppService(咱們在'構建Web API'部分建立的它)建立的javascript服務代理。

  ABP提供了基礎設施來在服務端和客戶端使用相同的本地化文本(參見文檔瞭解更多詳情)。

  vm.tasks爲tasks的列表,將在視圖中顯示。vm.refreshTasks方法經過使用taskService獲取任務填充這個數組。當selectedTaskState改變時調用它(使用$scope.$watch監視)。

  如你所見,調用一個應用服務方法很是簡單直接。這是ABP的一個特徵。它生成Web API層和Javascript代理。所以,咱們調用應用服務方法如同調用一個簡單的javascript方法。它與AngularJs(使用Angular的$http服務)徹底集成。

  讓咱們看看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")獲取「tasklist」的本地化文本(在服務端渲染HTML時獲取)。由於這是cshtml文件,因此能夠這麼作。

  ng-model綁定combobox和javascript變量。當變量改變時,combobox會隨之更新。當combobox改變時,變量也會被更新。這是AngularJs的雙向綁定。

  ng-repeat是Angular的另外一個指令,用來爲一個數列中的每一個值渲染相同的HTML。當數組改變時(例如添加一個項),它會自動體現到視圖。這是AngularJs另外一個強力特徵。

  注意:當你添加一個Javascript文件時(例如,"task list"控制器),須要把它添加到頁中。能夠把它添加到Home\Index.cshtml。

本地化

   ABP提供了一個靈活、強大的本地化系統。你可使用XML文件或資源文件做爲本地化源。你也能夠自定義本地化源。參見文檔瞭解更多。在這個示例應用中,我使用XML文件(它在web application的Localization文件夾中)

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="TaskSystem" value="Task System" />
    <text name="TaskList" value="Task List" />
    <text name="NewTask" value="New Task" />
    <text name="Xtasks" value="{0} tasks" />
    <text name="AllTasks" value="All tasks" />
    <text name="ActiveTasks" value="Active tasks" />
    <text name="CompletedTasks" value="Completed tasks" />
    <text name="TaskDescription" value="Task description" />
    <text name="EnterDescriptionHere" value="Task description" />
    <text name="AssignTo" value="Assign to" />
    <text name="SelectPerson" value="Select person" />
    <text name="CreateTheTask" value="Create the task" />
    <text name="TaskUpdatedMessage" value="Task has been successfully updated." />
    <text name="TaskCreatedMessage" value="Task {0} has been created successfully." />
  </texts>
</localizationDictionary>

單元測試

   ABP設計爲可測試的。我寫了一篇文章來展現ABP的單元集成測試。參見文章:Unit testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate

總結

   在本文中,我展現瞭如何使用SPA和響應式用戶界面發佈一個N層的ASP.NET MVC web應用。我使用了ABP,由於它使用最佳實踐簡化了發佈這樣的一個應用而且節省了咱們的時間。使用下面的鏈接獲取更多信息:

文章歷史

  • 2016-10-26: 更新ABP版本爲v1.0.
  • 2016-07-19: 更新文章和ABP版本爲v0.10.
  • 2015-06-08: 更新文章和ABP版本爲 v0.6.3.1.
  • 2015-02-20: 添加單元測試文章的鏈接,更新示例工程
  • 2015-01-05: 更新ABP版本爲 v0.5.
  • 2014-11-03: 更新文章和ABP版本爲v0.4.1.
  • 2014-09-08: 更新文章和ABP版本爲v0.3.2.
  • 2014-08-17: 更新ABP版本爲 v0.3.1.2.
  • 2014-07-22: 更新ABP版本爲 v0.3.0.1.
  • 2014-07-11: 添加 'Enable-Migrations' 命令的屏幕截圖.
  • 2014-07-08: 更新示例工程和文章.
  • 2014-07-01: 首次發佈文章.

參照

 [1] ASP.NET Boilerplate官方網站: http://www.aspnetboilerplate.com

相關文章
相關標籤/搜索