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

ASP.NET Core RC2 使用 WEB API 和 AngularJS - 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (23投票s)

2016年5月27日

CPOL

5分钟阅读

viewsIcon

95163

在本文中,您将了解 ASP.NET Core RC2 如何使用 WEB API 和 AngularJS。

引言

在本文中,我们将详细介绍如何使用 WEB API 和 AngularJS 创建 ASP.NET Core RC2。

关键点仍然是使用 .NET Framework 4.5.2 而不是 .NET Core。

您也可以查看我的下一篇文章

  • 如何替换 ASP.NET Core 的默认 Views 位置?
  • 如何将 ASP.NET Core RC2 部署到 IIS?
  • 如何将 NLog 集成到 ASP.NET Core 项目?

必备组件

Visual Studio 2015:您可以从 此处 下载。

.NET Core SDK:请从此链接 下载 .NET Core SDK。

.NET Framework 4.5.2:请从此链接 下载 .NET Framework 4.5.2。

.NodeJS:请从此链接 下载 NodeJS。

源代码:请从此链接 下载源代码。

在本文中,我们将详细介绍如何

  • 创建我们的 ASP.NET Core RC 2 Web 应用程序。
  • 如何添加我们的 WEB API 控制器。
  • 如何使用 NPM 添加 Javascript 包。
  • 如何配置 Gulp 文件。
  • 如何使用 Visual Studio 任务运行器资源管理器运行 Gulp 文件
  • 如何创建我们的 AngularJS 模块、控制器和服务文件,并获取 WEB API 数据以绑定到 MVC 页面。

下一部分将包含更多概念:第二部分

  • 身份验证
  • 使用 json 文件模拟服务 API
  •  

使用代码

创建数据库

在此版本中,我不会使用数据库,将其留到下一个版本。只是让它非常非常简单。

创建我们的 ASP.NET Core RC 2 Web 应用程序

安装完 Visual Studio 2015 和 .NET Core SDK 后,请运行您的 Visual Studio 2015。点击新建,然后选择项目,选择 Web,然后选择 ASP.NET Web 应用程序。输入您的项目名称,然后点击确定。

让我们稍微了解一下我的项目/解决方案名称

  • 解决方案名称:AspNetCoreSPA
  • 项目名称:AspNetCoreSPA.Web

这是我创建新 Web 项目时的规则。解决方案名称代表整个项目,应该简单且单一(不包含任何点)。项目名称代表每个项目的用途(在此,它是 MVC 的 Web)。项目名称以后可以更多,例如:AspNetCoreSPA.Business、AspNetCoreSPA.DataAccess、AspNetCoreSPA.Core。我将写另一篇文章,介绍使用 .NET Core 创建包含多个项目的解决方案。

然后选择“空”模板。我们将从头开始,以便了解我们将安装什么,我们将使用什么。

项目创建完成后,我们将创建如下的文件夹结构

我将详细解释我为什么创建这种结构

  • wwwroot 将包含所有客户端代码(js、html、css)。
    • 一个重要的提示:我们将不使用“.cshtml”(听起来很疯狂吧?),我们将只使用“.html”,而 AspNet 仅用作数据服务。
  • app:包含 html + js 文件(控制器、视图、模板等)。
  • styles:包含 css 文件
  • images:图片文件
  • scripts:包含我们自己的 js 文件:通用 js、...等
  • libraries:包含库。这些库是从 node_modules 复制的(详情)。
  • Configurations:代表任何配置类。
  • Controllers:包含 ASP.NET 控制器。

project.json 文件内容(什么是 project.json 文件?

{
  "userSecretsId": "aspnet-AspNetCoreSPA-c23d27a4-eb88-4b18-9b77-2a93f3b15119",

  "webroot": "wwwroot",
  "version": "1.0.0-*",

  "dependencies": {
    "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview1-final",
      "type": "build"
    },
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc2-final",
    "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
      "version": "1.0.0-preview1-final",
      "type": "build"
    },
    "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
      "version": "1.0.0-preview1-final",
      "type": "build"
    }
  },

  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.Extensions.SecretManager.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": [
        "portable-net45+win8+dnxcore50",
        "portable-net45+win8"
      ]
    }
  },

  "frameworks": {
    "net452": { }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

创建 package.json(什么是 package.json 文件?):您可以理解 package.json 类似于 Nuget Package Manger。在使用 npm 命令之前,您应该先了解一点 NodeJS。

package.json 的内容代表我们为此项目使用的库。

  • angular: 1.5.5
  • bootstrap: 3.3.6
  • devDependencies 代表我们在开发时使用的库。
{
  "version": "1.0.0",
  "name": "asp.net",
  "private": true,
  "dependencies": {
  },
  "devDependencies": {
    "gulp": "3.8.11",
    "gulp-inject": "4.1.0",
    "stream-series": "0.1.1",
    "wiredep": "4.0.0"
  }
}

也创建 bower.json:添加新项 -> Bower 配置文件

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "jquery": "2.2.4",
    "bootstrap": "3.3.6",
    "angular": "1.5.5",
    "angular-ui-router": "0.3.0"
  },
  "overrides":{
      "bootstrap":{
        "main": [
          "less/bootstrap.less",
          "dist/css/bootstrap.css",
          "dist/js/bootstrap.js"
        ]
      }
   }
}

为什么使用 Bower?Bower 是一个带有依赖项的 Javascript 包管理器:例如:Bootstrap 依赖于 jquery,...。

请记住更改 .bowerrc 的内容

.bowerrc 文件内容:这意味着 bower.json 中的所有依赖项都将被复制到“wwwroot/libraries”文件夹

{
    "directory": "wwwroot/libraries"
}

我们将使用wiredep 自动将 bower 库注入到 index.html 中,我们稍后会看到更多细节。

创建 appsettings.json:应用程序设置类似于 ASP.NET MVC 旧版本的 web.config。我稍后将解释如何使用此文件的内容。首先,您可以看到 DefaultConnection 是空的,因为我们没有使用任何数据库连接。

{
  "ConnectionStrings": {
    "DefaultConnection": ""
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

我们的 Program.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;

namespace AspNetCoreSPA.Web
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

我们的 Startup.cs

using AspNetCoreSPA.Web.Configurations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace AspNetCoreSPA.Web
{
    public class Startup
    {
        public IConfigurationRoot Configuration { get; }

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            Configuration = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.ReplaceDefaultViewEngine();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseStaticFiles();
   
            app.UseMvcWithDefaultRoute();
        }
    }
}

创建 gulpfile.js:右键单击我们的项目 -> 添加 -> 新项

gulpfile.js 文件内容

/// <binding BeforeBuild='inject:index' />
"use strict";

var gulp = require("gulp"),
    series = require('stream-series'),
    inject = require('gulp-inject'),
    wiredep = require('wiredep').stream;

var webroot = "./wwwroot/";

var paths = {
    ngModule: webroot + "app/**/*.module.js",
    ngRoute: webroot + "app/**/*.route.js",
    ngController: webroot + "app/**/*.controller.js",
    script: webroot + "scripts/**/*.js",
    style: webroot + "styles/**/*.css"
};

gulp.task('inject:index', function () {
    var moduleSrc = gulp.src(paths.ngModule, { read: false });
    var routeSrc = gulp.src(paths.ngRoute, { read: false });
    var controllerSrc = gulp.src(paths.ngController, { read: false });
    var scriptSrc = gulp.src(paths.script, { read: false });
    var styleSrc = gulp.src(paths.style, { read: false });

    gulp.src(webroot + 'app/index.html')
        .pipe(wiredep({
            optional: 'configuration',
            goes: 'here',
            ignorePath: '..'
        }))
        .pipe(inject(series(scriptSrc, moduleSrc, routeSrc, controllerSrc), { ignorePath: '/wwwroot' }))
        .pipe(inject(series(styleSrc), { ignorePath: '/wwwroot' }))
        .pipe(gulp.dest(webroot + 'app'));
});

关于 gulpfile.js 的更多详细信息

Series:用于订购 Javascript 文件:因为我们在 html 页面中包含 js 文件时,它必须是有序的。

Inject:用于将必要的库包含到我们的 html 页面中。

Wiredep:用于将 bower 库包含到我们的 html 页面中。

var gulp = require("gulp"),
    series = require('stream-series'),
    inject = require('gulp-inject'),
    wiredep = require('wiredep').stream;

现在来看主脚本

gulp.src(webroot + 'app/index.html')
        .pipe(wiredep({
            optional: 'configuration',
            goes: 'here',
            ignorePath: '..'
        }))
        .pipe(inject(series(scriptSrc, moduleSrc, routeSrc, controllerSrc), { ignorePath: '/wwwroot' }))
        .pipe(inject(series(styleSrc), { ignorePath: '/wwwroot' }))
        .pipe(gulp.dest(webroot + 'app'));

第 1 行:嘿 gulp,我们将使用 index.html 页面来注入 js 和 css,看看 index.html 的内容,您就会明白。

第 2 行:按依赖项排序注入 bower 库

<!-- bower:css -->
<!-- endbower -->

<!-- bower:js -->
<!-- endbower -->

第 3 行:注入

<!-- inject:css -->
<!-- endinject -->

<!-- inject:js -->
<!-- endinject -->

创建 index.html:右键单击 app -> 添加 -> 新项 -> HTML 页面 -> index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AspNetCoreSPA</title>

    <!-- bower:css -->
    <!-- endbower -->
    <!-- inject:css -->
    <!-- endinject -->
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <a href="#">AspNetCoreSPA</a>
            </div>
        </div>
    </div>
    <div class="container body-content">
        <div class="jumbotron">
            <h1>AspNetCoreSPA</h1>
            <p class="lead">Welcome to AspNetCoreSPA</p>
        </div>
        <div class="row">
            <h2>Student List</h2>
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>Firstname</th>
                        <th>Lastname</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>John</td>
                        <td>Doe</td>
                        <td>john@example.com</td>
                    </tr>
                    <tr>
                        <td>Mary</td>
                        <td>Moe</td>
                        <td>mary@example.com</td>
                    </tr>
                    <tr>
                        <td>July</td>
                        <td>Dooley</td>
                        <td>july@example.com</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <hr />
        <footer>
            <p>&copy; 2016 - Toan Manh Nguyen</p>
        </footer>
    </div>

    <!-- bower:js -->
    <!-- endbower -->
    <!-- inject:js -->
    <!-- endinject -->
</body>
</html>

在 wwwroot/styles/site.css 中添加 site.css

body {
    padding-top: 50px;
    padding-bottom: 20px;
}

/* Wrapping element */
/* Set some basic padding to keep content from hitting the edges */
.body-content {
    padding-left: 15px;
    padding-right: 15px;
}

/* Set widths on the form inputs since otherwise they're 100% wide */
input,
select,
textarea {
    max-width: 280px;
}

/* Styles for angular ui transition animations */
.angular-animation-container {
    position: relative;
}

.shuffle-animation.ng-enter,
.shuffle-animation.ng-leave {
    position: absolute;
}

.shuffle-animation.ng-enter {
    -moz-transition: ease-out all 0.3s 0.4s;
    -o-transition: ease-out all 0.3s 0.4s;
    -webkit-transition: ease-out all 0.3s 0.4s;
    -ms-transition: ease-out all 0.3s 0.4s;
    transition: ease-out all 0.3s 0.4s;
    left: 2em;
    opacity: 0;
}

    .shuffle-animation.ng-enter.ng-enter-active {
        left: 0;
        opacity: 1;
    }

.shuffle-animation.ng-leave {
    -moz-transition: 0.3s ease-out all;
    -o-transition: 0.3s ease-out all;
    -webkit-transition: 0.3s ease-out all;
    -ms-transition: 0.3s ease-out all;
    transition: 0.3s ease-out all;
    left: 0;
    opacity: 1;
}

    .shuffle-animation.ng-leave.ng-leave-active {
        left: 2em;
        -ms-opacity: 0;
        opacity: 0;
    }

创建 HomeController 以返回 index.html:右键单击 Controllers 文件夹 -> 添加 -> 新项 -> Class -> HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreSPA.Web.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View("index");
        }
    }
}

然后转到 View -> Other Windows -> Task Runner Explorer 查看 Gulpfile.js 的任务

然后右键单击 lib 并运行

运行 gulp 任务后,index.html 会发生一点变化

在 <head> 中

<!-- bower:css -->
<link rel="stylesheet" href="/libraries/bootstrap/dist/css/bootstrap.css" />
<!-- endbower -->
<!-- inject:css -->
<link rel="stylesheet" href="/styles/site.css">
<!-- endinject -->

在 <body> 中

<!-- bower:js -->
<script src="/libraries/jquery/dist/jquery.js"></script> 
<script src="/libraries/bootstrap/dist/js/bootstrap.js"></script> 
<script src="/libraries/angular/angular.js"></script> 
<script src="/libraries/angular-ui-router/release/angular-ui-router.js"></script> 
<!-- endbower --> 
<!-- inject:js --> 
<script src="/scripts/site.common.js"></script> 
<script src="/scripts/site.ng.js"></script> 
<script src="/app/index.module.js"></script> 
<script src="/app/index.route.js"></script> 
<script src="/app/main/main.controller.js"></script> 
<!-- endinject -->

酷!Gulp 自动将必要的代码按顺序注入到 index.html 页面中。

现在,我们来谈谈 AspNetCore 的默认 Views 文件夹

  • AspNetCore 的默认 Views 文件夹是:
  • "/Views/{1}/{0}" + ViewExtension"
    "/Views/Shared/{0}" + ViewExtension"
  • 但我们的视图位于 wwwroot/app,那么 AspNetCore 如何检测我们的视图呢?我们有一个 Bug在这里。解决方案如下
    • 扩展 RazorViewEngine
    • 将 IRazorViewEngine 的实例替换为 MyRazorViewEngine,我们的新位置是
    • "~/wwwroot/app/views/{1}/{0}.html"
      "~/wwwroot/app/{0}.html"
      
  • 您可以在上面链接的 github 中的我的代码中看到详细信息

现在,我们将 html 分割为主,index.html 只包含 css、js 和 body 标签。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AspNetCoreSPA</title>

    <script type="text/javascript">
        var site = site || {};
    </script>

    <!-- bower:css -->
    <!-- endbower -->
    <!-- inject:css -->
    <!-- endinject -->
</head>
<body ng-app="app">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <a href="#">AspNetCoreSPA</a>
            </div>
        </div>
    </div>
    <div class="container body-content">
        <div class="jumbotron">
            <h1>AspNetCoreSPA</h1>
            <p class="lead">Welcome to AspNetCoreSPA</p>
        </div>
        <div class="row">
            <div ui-view></div>
        </div>

        <hr />
        <footer>
            <p>&copy; 2016 - Toan Manh Nguyen</p>
        </footer>
    </div>

    <!-- bower:js -->
    <!-- endbower -->
    <!-- inject:js -->
    <!-- endinject -->
</body>
</html>

我们在 main 文件夹中创建一个 main.html 并将代码放入其中

main.html

<h2>Student List</h2>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John</td>
            <td>Doe</td>
            <td>john@example.com</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>Moe</td>
            <td>mary@example.com</td>
        </tr>
        <tr>
            <td>July</td>
            <td>Dooley</td>
            <td>july@example.com</td>
        </tr>
    </tbody>
</table>

我们创建 index.module.js 来配置 ui.router

(function () {
    'use strict';

    angular
        .module('app', [
            'ui.router'
        ]);

})();

我们创建 index.route.js

(function () {
    'use strict';

    angular
        .module('app')
        .config(routerConfig);

    routerConfig.$inject = ['$stateProvider', '$urlRouterProvider'];

    function routerConfig($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise('/');

        $stateProvider
          .state('home', {
              url: '/',
              templateUrl: 'app/main/main.html',
              controller: 'MainController',
              controllerAs: 'mainCtrl'
          });
    }
})();

现在让我们再次看看整个项目结构

再次,您可以在此处下载源代码。

让我们按 F5 键查看结果。

 

历史

2016/05/26 - 创建文章。

2016/05/28 - 重构文章并添加有关 Gulpfile.js 的更多详细信息

  • 我更改了 wwwroot 中的一些文件夹名称,例如 YeoMan 文件夹结构。请参考我的GitHub
© . All rights reserved.