受欢迎的博客标签
Introduction In this article we will see in detail how to create ASP.NET Core RC2 using WEB API and AngularJS. Key point is still using .NET Framework 4.5.2 instead of .NET Core. You can also view my next articles: How to replace default Views location of ASP.NET Core? How to publish ASP.NET Core RC2 to IIS? How to intergrate NLog to ASP.NET Core project? PrerequisitesVisual Studio 2015: You can download it from here. .NET Core SDK: download .NET Core SDK from this link. .NET Framework 4.5.2: download .NET Framework 4.5.2 from this link. .NodeJS: download NodeJS from this link. Sourcecode: download source from this link. In this article we will see in detail how to: Create our ASP.NET Core RC 2 Web Application. How to add our WEB API Controller. How to add javascript package using NPM. How to configure Gulp File. How to Run the Gulp file using Visual Studio Task Runner Explorer How to create our AngularJS Module, Controller and Service file and get WEB API data for bind in MVC page. Next part will more concept: Part 2 Using the code Create a Database In this version, i won't using Database, let it for the next version. Just make this very very simple. Create our ASP.NET Core RC 2 Web Application After installed both Visual Studio 2015 and .NET Core SDK, run your Visual Studio 2015. Click New, then Project, select Web and select ASP.NET Web Application. Enter your Project Name and click OK. Let see a little about my project/solution name: Solution name: AspNetCoreSPA Project name: AspNetCoreSPA.Web This is my rule when i create new web project. Solution name stand for whole project, it should be simple and single (not any dot included). Project name stand for purpose of each project (for this, it's web for MVC). Project name can be more later like: AspNetCoreSPA.Business, AspNetCoreSPA.DataAccess, AspNetCoreSPA.Core. I will write another article about creating solution with multiple project using .NET Core. Then select Empty template. We will start from scratch to understand what we will install, what we will use. After project created, we will create structure folder like this: I will explain more detail why i created this structure: wwwroot will contain all client code (js, html, css). A big note here: We won't use ".cshtml" (sound crazy huh?), we will use just ".html" and AspNet just for data service. app: include html + js file (Controller, view, template,...). styles: contain css files images: image files scripts: contain our own js files: common js, ...ect libraries: contain libraries. Those libraries were copied from node_modules (detail). Configurations: Stand for any configuration class. Controllers: contain ASP.NET Controller. Content of project.json (What is the project.json file?) Hide   Shrink    Copy Code { "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%" ] } } Create package.json (What is the package.json file?): You can understand that package.json is similar to Nuget Package Manger. Before using npm command, you should learn a little about NodeJS. Content of package.json is stand for the lib we used for this project. angular: 1.5.5 bootstrap: 3.3.6 devDependencies is stand for libs we used in development time. Hide   Copy Code { "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" } } Create bower.json too: Add New Item -> Bower Configuration File Hide   Copy Code { "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" ] } } } Why using Bower? Bower is a javascript package manager with dependencies: Example: Bootstrap depends on jquery,.... Remember change .bowerrc content: Content of .bowerrc: This mean all dependencies in bower.json will be copied to "wwwroot/libraries" folder Hide   Copy Code { "directory": "wwwroot/libraries" } We will use wiredep to automatically inject bower libraries to index.html, we will see more detail later. Create appsettings.json: App setting like web.config in previous version of ASP.NET MVC. I will explain how to use content of this file later. Firstly, you can see DefaultConnection is empty because we don't use any connection to DB. Hide   Copy Code { "ConnectionStrings": { "DefaultConnection": "" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } Our Program.cs Hide   Copy Code 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(); } } } Our Startup.cs Hide   Shrink    Copy Code 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(); } } } Create gulpfile.js: Right Click on our project -> Add New Item Content of gulpfile.js: Hide   Shrink    Copy Code /// <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')); }); More detail about gulpfile.js: Series: used for order javascript file: Because when we include js file in a html page, it must be ordered. Inject: used for include neccessary libraries into our html page. Wiredep: used for include bower libraries into our html page. Hide   Copy Code var gulp = require("gulp"), series = require('stream-series'), inject = require('gulp-inject'), wiredep = require('wiredep').stream; Now see the main script: Hide   Copy Code 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')); Line 1: Hey gulp, we will use index.html page for inject js and css, let look at the content of index.html and you will see. Line 2: Inject bower libraries sorted by dependencies Hide   Copy Code <!-- bower:css --> <!-- endbower --> <!-- bower:js --> <!-- endbower --> Line 3: Inject Hide   Copy Code <!-- inject:css --> <!-- endinject --> <!-- inject:js --> <!-- endinject --> Create index.html: Right click on app -> Add -> New Item -> HTML Page -> index.html Hide   Shrink    Copy Code <!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> Add site.css in wwwroot/styles/site.css Hide   Shrink    Copy Code 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; } Create HomeController to return index.html: Right click on Controllers folder -> Add -> New Item-> Class -> HomeController.cs Hide   Copy Code using Microsoft.AspNetCore.Mvc; namespace AspNetCoreSPA.Web.Controllers { public class HomeController : Controller { public IActionResult Index() { return View("index"); } } } Then go to View -> Other Windows -> Task Runner Explorer to see task of Gulpfile.js Then Right click on lib and Run: After run gulp task, the index.html will change a little: In <head> Hide   Copy Code <!-- bower:css --> <link rel="stylesheet" href="/libraries/bootstrap/dist/css/bootstrap.css" /> <!-- endbower --> <!-- inject:css --> <link rel="stylesheet" href="/styles/site.css"> <!-- endinject --> In <body> Hide   Copy Code <!-- bower:js --> <script src="/libraries/jquery/dist/jquery.js"></script> Cool!!! Gulp automatically inject necessary to index.html page with sorting. Now, let's talk about Default Views Folder of AspNetCore: Default Views folder of AspNetCore are:  Hide   Copy Code "/Views/{1}/{0}" + ViewExtension" "/Views/Shared/{0}" + ViewExtension" But our views located at wwwroot/app, so how AspNetCore can detect our views? We have a bug here. Solution below: Extend RazorViewEngine Replace instance of IRazorViewEngine to MyRazorViewEngine, our new locations are: Hide   Copy Code "~/wwwroot/app/views/{1}/{0}.html" "~/wwwroot/app/{0}.html" You can see detail in my code at github at link above Now, we will separate html into main, the index.html just contain css, js, and body tag. index.html Hide   Shrink    Copy Code <!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> Let's create main.html in main folder and put code in: main.html Hide   Copy Code <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> We create index.module.js to config ui.router Hide   Copy Code (function () { 'use strict'; angular .module('app', [ 'ui.router' ]); })(); We create index.route.js Hide   Copy Code (function () { 'use strict'; angular .module('app') .config(routerConfig); function routerConfig($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/'); $stateProvider .state('home', { url: '/', templateUrl: 'app/main/main.html', controller: 'MainController', controllerAs: 'mainCtrl' }); } })(); Now let's see the whole project structure again: Once again, you can download source-code here. Let's hit F5 and see the result.   History 2016/05/26 - Create article. 2016/05/28 - Re-structure article and add more detail about Gulpfile.js I changed some folder name in wwwroot like YeoMan folder structure. Please refer in my GitHub  .