65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.Net Core - AngularJs 构建单页应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (8投票s)

2018年3月29日

CPOL

6分钟阅读

viewsIcon

51205

downloadIcon

2563

在本篇文章中,我们将使用 AngularJS 和 ASP.Net Core 来创建一个单页应用程序(SPA)。我们将使用 Angular-UI-Router 来处理我们的应用程序路由,而不是 MVC 路由。

如果您是 ASP.Net Core 新手,请在此处获取完整的入门概述:http://shashangka.com/2016/11/26/net-core-startup/

先决条件: 在开始之前,请确保开发环境已正确准备。以下是开发示例应用程序所需的先决条件列表。

  1. Visual Studio 2017
  2. .NET Core 2.0
  3. NodeJS
  4. MSSQL 2016
  5. IIS

要点: 为了开发示例应用程序,将特别关注以下几点,其中列出了关键机制。

  1. 包管理
  2. 通过 Gulp 传输库
  3. 使用 AngularJs 应用
  4. 添加中间件
  5. 脚手架 MSSQL 数据库
  6. Web-API
  7. 启用 CORS
  8. 使用 Gulp 构建 AngularJS 应用
  9. 测试应用程序(CRUD)
  10. 发布到 IIS

开始: 让我们开始吧,打开 Visual Studio 创建新项目,转到 > 文件 > 新建 > 项目,然后选择创建 ASP.Net Core 应用程序的项目,使用 ASP.Net Core 2.0 MVC Web 应用程序,如下图所示。

最初,应用程序的文件夹结构可能看起来像这样,我们将根据我们的需求对其进行修改。

按照我们的计划,我们将使用 AngularJS 路由通过 ASP.NET 静态文件服务来创建单页应用程序。

我们需要指向一个静态页面来接收所有请求,然后 Angular 将处理请求并决定提供哪个页面。让我们在 *wwwroot* 文件夹中创建一个 html 页面。这将是我们加载到客户端请求上的主页面。

提供默认页面

现在我们需要修改 *Startup.cs* 文件以添加用于提供静态 html 页面的中间件。以下是用于提供页面的代码片段。在此应用中,我已将其创建为“*index.html*”。

DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("/index.html");
app.UseDefaultFiles(options);

处理客户端路由回退的中间件: 为了在 AngularJS SPA 应用中重新加载页面时避免 404 错误,我们需要添加中间件来处理客户端路由回退。下面的代码片段将负责这一点。

app.Use(async (context, next) =>
{
    await next();
    if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
    {
        context.Request.Path = "/index.html";
        context.Response.StatusCode = 200;
        await next();
    }
});

在此处获取有关中间件的更多详细信息:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?tabs=aspnetcore2x

如果我们运行应用程序进行测试,将默认提供静态页面。现在让我们开始客户端包管理。

客户端

包管理: 我们需要添加一些前端包,如 AngularJS,正如我们所见,我们的初始模板没有任何类似的东西。

要添加新包,请右键单击项目,然后单击“*管理 Bower 包*” 通过逐个搜索来安装所需的包。最后,已安装的包将列出,如下面的屏幕截图所示。

让我们向我们的应用程序添加一些 Node 包,首先我们需要在项目中添加一个 npm 配置文件。右键单击项目,转到 > 添加 > 新项目。在新建项目窗口中,选择 npm 配置文件,如下图所示。 将所有这些包添加到 devDependencies,它将自动安装到我们的项目中。

"gulp": "^3.9.1",
"gulp-concat": "^2.6.1",
"gulp-rename": "^1.2.2",
"gulp-cssmin": "^0.2.0",
"gulp-uglify": "^3.0.0",
"gulp-htmlclean": "^2.7.20",
"rimraf": "^2.6.2"

最后,我们的包依赖项已列出。

让我们将所需的库从 bower_componen 文件夹传输到“wwwroot”,以便在主 html 页面中调用它们。添加一个 gulp 文件,然后复制下面的代码片段并粘贴到新添加的文件中。

/// <binding AfterBuild='build-all' />

var gulp = require("gulp"),
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify"),
    rename = require("gulp-rename");

var root_path = {
    webroot: "./wwwroot/"
};

//library source
root_path.nmSrc = "./bower_components/";

//library destination
root_path.package_lib = root_path.webroot + "lib/";


gulp.task('copy-lib-css', function () {
    gulp.src('./bower_components/bootstrap/dist/css/bootstrap.min.css')
        .pipe(gulp.dest(root_path.webroot + '/css/'));
    gulp.src('./bower_components/toastr/toastr.min.css')
        .pipe(gulp.dest(root_path.webroot + '/css/'));
});

gulp.task('copy-lib-js', function () {
    gulp.src('./bower_components/jquery/dist/jquery.min.js')
        .pipe(gulp.dest(root_path.package_lib + '/jquery/'));

    gulp.src('./bower_components/bootstrap/dist/js/bootstrap.min.js')
        .pipe(gulp.dest(root_path.package_lib + '/bootstrap/'));

    gulp.src('./bower_components/toastr/toastr.min.js')
        .pipe(gulp.dest(root_path.package_lib + '/toastr/'));

    gulp.src('./bower_components/angular/angular.min.js')
        .pipe(gulp.dest(root_path.package_lib + 'angular/'));
    gulp.src('./bower_components/angular-ui-router/release/angular-ui-router.min.js')
        .pipe(gulp.dest(root_path.package_lib + 'angular/'));
});

gulp.task("copy-all", ["copy-lib-css", "copy-lib-js"]);
//Copy End

现在,从 Visual Studio 的顶部菜单中,转到 > 视图 > 其他窗口 > 任务运行程序资源管理器。让我们通过刷新获取任务列表,然后像下面的屏幕截图一样运行任务。

正如我们从图中看到的,库已传输到根文件夹。我们使用了一个管理模板,因此其他库列在“*js*”文件夹中。

我们现在将在主布局页面中添加 AngularJs 库引用。如下所示。

<!DOCTYPE html>
<html lang="en" ng-app="templating_app">
   <head></head>
   <body>
	<!-- Core JS Files   -->
<script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
<script src="/lib/bootstrap/tether.min.js"></script>
<script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>

<!-- App JS Files   -->
<script src="/lib/angular/angular.min.js"></script>
<script src="/lib/angular/angular-ui-router.min.js"></script>
   </body>
</html>

AngularJS 应用程序: 让我们开始使用 AngularJS 应用程序,创建一个名为“*app*”的新文件夹。 “app”文件夹将保存我们所有的前端开发文件。让我们创建所有必需的文件和文件夹。在 shared 文件夹中,我们添加了侧边栏/顶部栏菜单之类的部分视图,这些视图将由 angular 指令调用。

模块: 这将定义我们的应用程序。

var templatingApp;
(
    function () {
        'use strict';
        templatingApp = angular.module('templating_app', ['ui.router']);
    }
)();

路由: 此文件将处理来自 URL 的应用程序路由。

templatingApp.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', '$compileProvider',
    function ($locationProvider, $stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $compileProvider) {

        //console.log('Appt.Main is now running')
        if (window.history && window.history.pushState) {
            $locationProvider.html5Mode({
                enabled: true,
                requireBase: true
            }).hashPrefix('!');
        };
        $urlMatcherFactoryProvider.strictMode(false);
        $compileProvider.debugInfoEnabled(false);

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: './views/home/home.html',
                controller: 'HomeController'
            })
            .state('dashboard', {
                url: '/dashboard',
                templateUrl: './views/home/home.html',
                controller: 'HomeController'
            })
            .state('user', {
                url: '/user',
                templateUrl: './views/user/user.html',
                controller: 'UserController'
            })
            .state('about', {
                url: '/about',
                templateUrl: './views/about/about.html',
                controller: 'AboutController'
            });

        $urlRouterProvider.otherwise('/home');
    }]); 

路由问题: 此文件将处理来自 URL 的应用程序路由。在 AngularJs 中,我们需要启用 HTML5 模式以删除 URL 中的 /#!/ 符号。下面的代码片段用于启用此模式。

$locationProvider.html5Mode({
    enabled: true,
    requireBase: true
}).hashPrefix('!');

我们还需要在主页面中指定 base。

<base href="/">

指令:用于顶部导航栏:

templatingApp.directive("navbarMenu", function () {
    return {
        restrict: 'E',
        templateUrl: 'views/shared/navbar/nav.html'
    };
});

用于顶部侧边栏

templatingApp.directive("sidebarMenu", function () {
    return {
        restrict: 'E',
        templateUrl: 'views/shared/sidebar/menu.html'
    };
});

Angular Controller: 这是 Angular Controller,它将管理视图并执行从客户端到服务器的所有 http 调用。

templatingApp.controller('UserController', ['$scope', '$http', function ($scope, $http) {
    $scope.title = "All User";
    $scope.ListUser = null;
    $scope.userModel = {};
    $scope.userModel.id = 0;
    getallData();

    //******=========Get All User=========******
    function getallData() {
        $http({
            method: 'GET',
            url: '/api/Values/GetUser/'
        }).then(function (response) {
            $scope.ListUser = response.data;
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Get Single User=========******
    $scope.getUser = function (user) {
        $http({
            method: 'GET',
            url: '/api/Values/GetUserByID/' + parseInt(user.id)
        }).then(function (response) {
            $scope.userModel = response.data;
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Save User=========******
    $scope.saveUser = function () {
        $http({
            method: 'POST',
            url: '/api/Values/PostUser/',
            data: $scope.userModel
        }).then(function (response) {
            $scope.reset();
            getallData();
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Update User=========******
    $scope.updateUser = function () {
        $http({
            method: 'PUT',
            url: '/api/Values/PutUser/' + parseInt($scope.userModel.id),
            data: $scope.userModel
        }).then(function (response) {
            $scope.reset();
            getallData();
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Delete User=========******
    $scope.deleteUser = function (user) {
        var IsConf = confirm('You are about to delete ' + user.Name + '. Are you sure?');
        if (IsConf) {
            $http({
                method: 'DELETE',
                url: '/api/Values/DeleteUserByID/' + parseInt(user.id)
            }).then(function (response) {
                $scope.reset();
                getallData();
            }, function (error) {
                console.log(error);
            });
        }
    };

    //******=========Clear Form=========******
    $scope.reset = function () {
        var msg = "Form Cleared";
        $scope.userModel = {};
        $scope.userModel.id = 0;
    };
}]);

服务器端

数据库: 让我们在 MSSQL Server 中创建一个数据库。这是我们存储数据的表。

CREATE TABLE [dbo].[User](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](250) NULL,
	[Email] [nvarchar](250) NULL,
	[Phone] [nvarchar](50) NULL
) ON [PRIMARY]
GO

在后端,我们将使用反向工程从现有数据库生成 EF 模型。

Entity Framework: 在我们将 Entity Framework 用于我们的应用程序之前,我们需要安装包。让我们右键单击项目,然后转到 > 工具 > NuGet 包管理器 > 程序包管理器控制台,逐个安装以下包。

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
  • Install-Package Microsoft.EntityFrameworkCore.Tools.DotNet

安装后,.csproj 文件将如下所示。 

NUGET 包

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.5" />
  <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
</ItemGroup>

<ItemGroup>
  <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
  <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>

EF 模型: 让我们使用程序包管理器控制台运行以下命令。

dotnet ef dbcontext scaffold "Server=DESKTOP-80DEJMQ;Database=dbCore;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer –-output-dir Models

现在打开 DbContext 文件,然后添加一个构造函数以将配置(如连接字符串)传递到 DbContext。

public dbCoreContext(DbContextOptions<dbCoreContext> options) :
             base(options){
        }

注册 DbContext: 在 Startup.cs 中,让我们将 DbContext 添加为服务以启用数据库连接。

//Database Connection
var connection = @"Server=DESKTOP-80DEJMQ;Database=dbCore;Trusted_Connection=True;";
services.AddDbContext<dbCoreContext>(options => options.UseSqlServer(connection));

启用 CORS: 为了从其他域访问 API,我们启用了 CORS。我们在 Startup.cs 的 ConfigureServices 方法中添加了服务。

services.AddCors(o => o.AddPolicy("AppPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));

CORS 机制

图片来源:https://mdn.org.cn/en-US/docs/Web/HTTP/CORS

API: 这是我们的 MVC API Controller,它全局使用了特定的 RoutePrefix 属性。通过这个 api Controller 类,我们使用 Entity Framework DbContext 执行数据库操作。

[Route("api/Values"), Produces("application/json"), EnableCors("AppPolicy")]
public class ValuesController : Controller
{
    private dbCoreContext _ctx = null;
    public ValuesController(dbCoreContext context)
    {
        _ctx = context;
    }


    // GET: api/Values/GetUser
    [HttpGet, Route("GetUser")]
    public async Task<object> GetUser()
    {
        List<User> users = null;
        object result = null;
        try
        {
            using (_ctx)
            {
                users = await _ctx.User.ToListAsync();
                result = new
                {
                    User
                };
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
        return users;
    }

    // GET api/Values/GetUserByID/5
    [HttpGet, Route("GetUserByID/{id}")]
    public async Task<User> GetUserByID(int id)
    {
        User user = null;
        try
        {
            using (_ctx)
            {
                user = await _ctx.User.FirstOrDefaultAsync(x => x.Id == id);
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
        return user;
    }


    // POST api/Values/PostUser
    [HttpPost, Route("PostUser")]
    public async Task<object> PostUser([FromBody]User model)
    {
        object result = null; string message = "";
        if (model == null)
        {
            return BadRequest();
        }
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    _ctx.User.Add(model);
                    await _ctx.SaveChangesAsync();
                    _ctxTransaction.Commit();
                    message = "Saved Successfully";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback();
                    e.ToString();
                    message = "Saved Error";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }

    // PUT api/Values/PutUser/5
    [HttpPut, Route("PutUser/{id}")]
    public async Task<object> PutUser(int id, [FromBody]User model)
    {
        object result = null; string message = "";
        if (model == null)
        {
            return BadRequest();
        }
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    var entityUpdate = _ctx.User.FirstOrDefault(x => x.Id == id);
                    if (entityUpdate != null)
                    {
                        entityUpdate.Name = model.Name;
                        entityUpdate.Phone = model.Phone;
                        entityUpdate.Email = model.Email;

                        await _ctx.SaveChangesAsync();
                    }
                    _ctxTransaction.Commit();
                    message = "Entry Updated";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback(); e.ToString();
                    message = "Entry Update Failed!!";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }

    // DELETE api/Values/DeleteUserByID/5
    [HttpDelete, Route("DeleteUserByID/{id}")]
    public async Task<object> DeleteUserByID(int id)
    {
        object result = null; string message = "";
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    var idToRemove = _ctx.User.SingleOrDefault(x => x.Id == id);
                    if (idToRemove != null)
                    {
                        _ctx.User.Remove(idToRemove);
                        await _ctx.SaveChangesAsync();
                    }
                    _ctxTransaction.Commit();
                    message = "Deleted Successfully";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback(); e.ToString();
                    message = "Error on Deleting!!";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }
}

我们已经完成了应用程序步骤,现在是时候将我们的应用程序缩小到一个 js 文件,然后将其引用到主 html 页面了。 

修改 Gulp: 将下面的代码片段添加到现有的 gulp 文件,转到 > TaskRunnerExplorer 然后刷新窗口,它将在我们构建应用程序时起作用,因为它已绑定。

gulp.task('min-js', function () {
    gulp.src(['./app/**/*.js'])
        .pipe(concat('app.js'))
        .pipe(uglify())
        .pipe(gulp.dest(root_path.webroot))
});

gulp.task('copy-folder-html', function () {
    gulp.src('app/**/*.html')
        .pipe(gulp.dest(root_path.webroot + 'views'));
});

gulp.task("build-all", ["min-js", "copy-folder-html"]);
//Build End

正如我们从下面的屏幕截图看到的,构建已成功完成。

构建应用程序后,“*wwwroot*”文件夹将如下所示。

最终主 HTML: 让我们将 app.js 文件添加到主 html 页面。

<!DOCTYPE html>
<html lang="en" ng-app="templating_app">
   <head>
   </head>
   <body>
	<!-- Core JS Files   -->
<script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
<script src="/lib/bootstrap/tether.min.js"></script>
<script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>

<!-- App JS Files   -->
<script src="/lib/angular/angular.min.js"></script>
<script src="/lib/angular/angular-ui-router.min.js"></script>
<script src="/app.js"></script>   
   </body>
</html>

输出: 在这里,我们可以看到用户数据已在网格中列出,这些数据也可以从其他域或设备访问。 

发布到 IIS: 右键单击项目文件,转到 > 发布窗口,选择要发布的类型。我选择了文件夹发布。

现在打开 IIS 添加新网站

转到应用程序池,确保 .Net CLR 版本为 2.0,如下图所示。

解决 IIS 中的底层提供程序问题:转到 URL:http://shashangka.com/2015/12/15/the-underlying-provider-failed-on-open-only-under-iis

让我们从 IIS 浏览网站。

希望这会有所帮助 🙂

参考

© . All rights reserved.