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






4.83/5 (8投票s)
在本篇文章中,我们将使用 AngularJS 和 ASP.Net Core 来创建一个单页应用程序(SPA)。我们将使用 Angular-UI-Router 来处理我们的应用程序路由,而不是 MVC 路由。
如果您是 ASP.Net Core 新手,请在此处获取完整的入门概述:http://shashangka.com/2016/11/26/net-core-startup/
先决条件: 在开始之前,请确保开发环境已正确准备。以下是开发示例应用程序所需的先决条件列表。
要点: 为了开发示例应用程序,将特别关注以下几点,其中列出了关键机制。
- 包管理
- 通过 Gulp 传输库
- 使用 AngularJs 应用
- 添加中间件
- 脚手架 MSSQL 数据库
- Web-API
- 启用 CORS
- 使用 Gulp 构建 AngularJS 应用
- 测试应用程序(CRUD)
- 发布到 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: 右键单击项目文件,转到 > 发布窗口,选择要发布的类型。我选择了文件夹发布。
转到应用程序池,确保 .Net CLR 版本为 2.0,如下图所示。
解决 IIS 中的底层提供程序问题:转到 URL:http://shashangka.com/2015/12/15/the-underlying-provider-failed-on-open-only-under-iis
让我们从 IIS 浏览网站。
希望这会有所帮助 🙂
参考
- http://shashangka.com/2016/08/12/crud-using-net-core-angularjs2-webapi
- http://shashangka.com/2016/11/26/net-core-startup
- https://benjii.me/2016/01/angular2-routing-with-asp-net-core-1
- https://docs.microsoft.com/en-us/aspnet/core/security/cors
- https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
- https://c-sharpcorner.com/article/enable-cross-origin-resource-sharing-cors-in-asp-net-core