diff --git a/aspnetcore/client-side/index.md b/aspnetcore/client-side/index.md
index 0dba8b931a64..3313a719a77d 100644
--- a/aspnetcore/client-side/index.md
+++ b/aspnetcore/client-side/index.md
@@ -18,7 +18,8 @@ ms.prod: asp.net-core
- [Manage client-side packages with Bower](bower.md)
- [Building beautiful, responsive sites with Bootstrap](bootstrap.md)
- [Knockout.js MVVM Framework](knockout.md)
-- [Using AngularJS for Single Page Applications (SPAs)](angular.md)
+- [Using AngularJS for Single Page Apps (SPAs)](angular.md)
+- [Using JavaScriptServices for Single Page Apps (SPAs)](spa-services.md)
- [Styling applications with Less, Sass, and Font Awesome](less-sass-fa.md)
- [Bundling and minification](bundling-and-minification.md)
- [TypeScript](https://www.typescriptlang.org/docs/handbook/asp-net-core.html)
diff --git a/aspnetcore/client-side/spa-services.md b/aspnetcore/client-side/spa-services.md
new file mode 100644
index 000000000000..a2277d2b9fba
--- /dev/null
+++ b/aspnetcore/client-side/spa-services.md
@@ -0,0 +1,329 @@
+---
+title: Using JavaScriptServices for Creating Single Page Applications | Microsoft Docs
+author: scottaddie
+description: Learn about the benefits of using JavaScriptServices to build a SPA with ASP.NET Core
+keywords: ASP.NET Core, Angular, SPA, JavaScriptServices, SpaServices
+ms.author: scaddie
+manager: wpickett
+ms.date: 6/23/2017
+ms.topic: article
+ms.assetid: 4b30576b-2718-4c39-9253-a59966747893
+ms.technology: aspnet
+ms.prod: asp.net-core
+uid: client-side/spa-services
+ms.custom: H1Hack27Feb2017
+---
+# Using JavaScriptServices for Creating Single Page Applications with ASP.NET Core
+
+By [Scott Addie](https://github.com/scottaddie) and [Fiyaz Hasan](http://fiyazhasan.me/)
+
+A Single Page Application (SPA) is a popular type of web application due to its inherent rich user experience. Integrating client-side SPA frameworks or libraries, such as [Angular](https://angular.io/) or [React](https://facebook.github.io/react/), with server-side frameworks like ASP.NET Core can be difficult. [JavaScriptServices](https://github.com/aspnet/JavaScriptServices) was developed to reduce friction in the integration process. It enables seamless operation between the different client and server technology stacks.
+
+[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/client-side/spa-services/sample)
+
+
+
+## What is JavaScriptServices?
+
+JavaScriptServices is a collection of client-side technologies for ASP.NET Core. Its goal is to position ASP.NET Core as developers' preferred server-side platform for building SPAs.
+
+JavaScriptServices consists of three distinct NuGet packages:
+* [Microsoft.AspNetCore.NodeServices](http://www.nuget.org/packages/Microsoft.AspNetCore.NodeServices/) (NodeServices)
+* [Microsoft.AspNetCore.SpaServices](http://www.nuget.org/packages/Microsoft.AspNetCore.SpaServices/) (SpaServices)
+* [Microsoft.AspNetCore.SpaTemplates](http://www.nuget.org/packages/Microsoft.AspNetCore.SpaTemplates/) (SpaTemplates)
+
+These packages are useful if you:
+* Run JavaScript on the server
+* Use a SPA framework or library
+* Build client-side assets with Webpack
+
+Much of the focus in this article is placed on using the SpaServices package.
+
+
+
+## What is SpaServices?
+
+SpaServices was created to position ASP.NET Core as developers' preferred server-side platform for building SPAs. SpaServices is not required to develop SPAs with ASP.NET Core, and it doesn't lock you into a particular client framework.
+
+SpaServices provides useful infrastructure such as:
+* [Server-side prerendering](#server-prerendering)
+* [Webpack Dev Middleware](#webpack-dev-middleware)
+* [Hot Module Replacement](#hot-module-replacement)
+* [Routing helpers](#routing-helpers)
+
+Collectively, these infrastructure components enhance both the development workflow and the runtime experience. The components can be adopted individually.
+
+
+
+## Prerequisites for using SpaServices
+
+To work with SpaServices, install the following:
+* [Node.js](https://nodejs.org/) (version 6 or later) with npm
+ * To verify these components are installed and can be found, run the following from the command line:
+
+ ```console
+ node -v && npm -v
+ ```
+
+Note: If you're deploying to an Azure web site, you don't need to do anything here — Node.js is installed and available in the server environments.
+
+* [.NET Core SDK](https://www.microsoft.com/net/download/core) 1.0 (or later)
+ * If you're on Windows, this can be installed by selecting Visual Studio 2017's **.NET Core cross-platform development** workload.
+
+* [Microsoft.AspNetCore.SpaServices](http://www.nuget.org/packages/Microsoft.AspNetCore.SpaServices/) NuGet package
+
+
+
+## Server-side prerendering
+
+A universal (also known as isomorphic) application is a JavaScript application capable of running both on the server and the client. Angular, React, and other popular frameworks provide a universal platform for this application development style. The idea is to first render the framework components on the server via Node.js, and then delegate further execution to the client.
+
+ASP.NET Core [Tag Helpers](xref:mvc/views/tag-helpers/intro) provided by SpaServices simplify the implementation of server-side prerendering by invoking the JavaScript functions on the server.
+
+### Prerequisites
+
+Install the following:
+* [aspnet-prerendering](https://www.npmjs.com/package/aspnet-prerendering) npm package:
+
+ ```console
+ npm i -S aspnet-prerendering
+ ```
+
+### Configuration
+
+The Tag Helpers are made discoverable via namespace registration in the project's *_ViewImports.cshtml* file:
+
+[!code-csharp[Main](../client-side/spa-services/sample/SpaServicesSampleApp/Views/_ViewImports.cshtml?highlight=3)]
+
+These Tag Helpers abstract the intricacies of communicating directly with low-level APIs by leveraging an HTML-like syntax inside the Razor view:
+
+[!code-html[Main](../client-side/spa-services/sample/SpaServicesSampleApp/Views/Home/Index.cshtml?range=5)]
+
+Microsoft's [Razor Language Services](https://marketplace.visualstudio.com/items?itemName=ms-madsk.RazorLanguageServices) extension improves Visual Studio 2017's Tag Helpers development experience by adding context-aware IntelliSense and syntax highlighting:
+![Tag Helpers intellisense](../client-side/spa-services/_static/tag_helper_intellisense.png)
+
+### The `asp-prerender-module` Tag Helper
+
+The `asp-prerender-module` Tag Helper, used in the preceding code example, executes *ClientApp/dist/main-server.js* on the server via Node.js. For clarity's sake, *main-server.js* file is an artifact of the TypeScript-to-JavaScript transpilation task in the [Webpack](http://webpack.github.io/) build process. Webpack defines an entry point alias of `main-server`; and, traversal of the dependency graph for this alias begins at the *ClientApp/boot-server.ts* file:
+
+[!code-javascript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/webpack.config.js?range=53)]
+
+In the following Angular example, the *ClientApp/boot-server.ts* file utilizes the `createServerRenderer` function and `RenderResult` type of the `aspnet-prerendering` npm package to configure server rendering via Node.js. The HTML markup destined for server-side rendering is passed to a resolve function call, which is wrapped in a strongly-typed JavaScript `Promise` object. The `Promise` object's significance is that it asynchronously supplies the HTML markup to the page for injection in the DOM's placeholder element.
+
+[!code-typescript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts?range=6,10-34,79-)]
+
+### The `asp-prerender-data` Tag Helper
+
+When coupled with the `asp-prerender-module` Tag Helper, the `asp-prerender-data` Tag Helper can be used to pass contextual information from the Razor view to the server-side JavaScript. For example, the following markup passes user data to the `main-server` module:
+
+[!code-html[Main](../client-side/spa-services/sample/SpaServicesSampleApp/Views/Home/Index.cshtml?range=9-12)]
+
+The received `UserName` argument is serialized using the built-in JSON serializer and is stored in the `params.data` object. In the following Angular example, the data is used to construct a personalized greeting within an `h1` element:
+
+[!code-typescript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts?range=6,10-21,38-52,79-)]
+
+Note: Property names passed in Tag Helpers are represented with **PascalCase** notation. Contrast that to JavaScript, where the same property names are represented with **camelCase**. The default JSON serialization configuration is responsible for this difference.
+
+To expand upon the preceding code example, data can be passed from the server to the view by hydrating the `globals` property provided to the `resolve` function:
+
+[!code-typescript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts?range=6,10-21,57-77,79-)]
+
+The `postList` array defined inside the `globals` object is attached to the browser's global `window` object. This variable hoisting to global scope eliminates duplication of effort, particularly as it pertains to loading the same data once on the server and again on the client.
+
+![global postList variable attached to window object](spa-services/_static/global_variable.png)
+
+
+
+## Webpack Dev Middleware
+
+[Webpack Dev Middleware](https://webpack.github.io/docs/webpack-dev-middleware.html) introduces a streamlined development workflow whereby Webpack builds resources on demand. The middleware automatically compiles and serves client-side resources when a page is reloaded in the browser. The alternate approach is to manually invoke Webpack via the project's npm build script when a third-party dependency or the custom code changes. An npm build script in the *package.json* file is shown in the following example:
+
+[!code-json[Main](../client-side/spa-services/sample/SpaServicesSampleApp/package.json?range=5)]
+
+### Prerequisites
+
+Install the following:
+* [aspnet-webpack](https://www.npmjs.com/package/aspnet-webpack) npm package:
+
+ ```console
+ npm i -D aspnet-webpack
+ ```
+
+### Configuration
+
+Webpack Dev Middleware is registered into the HTTP request pipeline via the following code in the *Startup.cs* file's `Configure` method:
+
+[!code-csharp[Main](../client-side/spa-services/sample/SpaServicesSampleApp/Startup.cs?name=webpack-middleware-registration&highlight=4)]
+
+The `UseWebpackDevMiddleware` extension method must be called before [registering static file hosting](xref:fundamentals/static-files) via the `UseStaticFiles` extension method. For security reasons, register the middleware only when the app runs in development mode.
+
+The *webpack.config.js* file's `output.publicPath` property tells the middleware to watch the `dist` folder for changes:
+
+[!code-javascript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/webpack.config.js?range=6,13-16)]
+
+
+
+## Hot Module Replacement
+
+Think of Webpack's [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html) (HMR) feature as an evolution of [Webpack Dev Middleware](#webpack-dev-middleware). HMR introduces all the same benefits, but it further streamlines the development workflow by automatically updating page content after compiling the changes. Don't confuse this with a refresh of the browser, which would interfere with the current in-memory state and debugging session of the SPA. There is a live link between the Webpack Dev Middleware service and the browser, which means changes are ~simply another banned word~ pushed to the browser.
+
+### Prerequisites
+
+Install the following:
+* [webpack-hot-middleware](https://www.npmjs.com/package/webpack-hot-middleware) npm package:
+
+ ```console
+ npm i -D webpack-hot-middleware
+ ```
+
+### Configuration
+
+The HMR component must be registered into MVC's HTTP request pipeline in the `Configure` method:
+
+```csharp
+app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
+ HotModuleReplacement = true
+});
+```
+
+As was true with [Webpack Dev Middleware](#webpack-dev-middleware), the `UseWebpackDevMiddleware` extension method must be called before the `UseStaticFiles` extension method. For security reasons, register the middleware only when the app runs in development mode.
+
+The *webpack.config.js* file must define a `plugins` array, even if it's left empty:
+
+[!code-javascript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/webpack.config.js?range=6,25)]
+
+After loading the app in the browser, the developer tools' Console tab provides confirmation of HMR activation:
+
+![Hot Module Replacement connected message](spa-services/_static/hmr_connected.png)
+
+
+
+## Routing helpers
+
+In most ASP.NET Core-based SPAs, you'll want client-side routing in addition to server-side routing. The SPA and MVC routing systems can work independently without interference. There is, however, one edge case posing challenges: identifying 404 HTTP responses.
+
+Consider the scenario in which an extensionless route of `/some/page` is used. Assume the request doesn't pattern-match a server-side route, but its pattern does match a client-side route. Now consider an incoming request for `/images/user-512.png`, which generally expects to find an image file on the server. If that requested resource path doesn't match any server-side route or static file, it's unlikely that the client-side application would handle it — you generally want to return a 404 HTTP status code.
+
+### Prerequisites
+
+Install the following:
+* The client-side routing npm package. Using Angular as an example:
+
+ ```console
+ npm i -S @angular/router
+ ```
+
+### Configuration
+
+An extension method named `MapSpaFallbackRoute` is used in the `Configure` method:
+
+[!code-csharp[Main](../client-side/spa-services/sample/SpaServicesSampleApp/Startup.cs?name=mvc-routing-table&highlight=7-9)]
+
+Tip: Routes are evaluated in the order in which they're configured. Consequently, the `default` route in the preceding code example is used first for pattern matching.
+
+
+
+## Creating a new project
+
+JavaScriptServices provides pre-configured application templates. SpaServices is used in these templates, in conjunction with different frameworks and libraries such as Angular, Aurelia, Knockout, React, and Vue.
+
+These templates can be installed via the .NET Core CLI by running the following command:
+
+```console
+dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
+```
+
+A list of available SPA templates is displayed:
+
+| Templates | Short Name | Language | Tags |
+|:------------------------------------------|:-----------|:---------|:------------|
+| MVC ASP.NET Core with Angular | angular | [C#] | Web/MVC/SPA |
+| MVC ASP.NET Core with Aurelia | aurelia | [C#] | Web/MVC/SPA |
+| MVC ASP.NET Core with Knockout.js | knockout | [C#] | Web/MVC/SPA |
+| MVC ASP.NET Core with React.js | react | [C#] | Web/MVC/SPA |
+| MVC ASP.NET Core with React.js and Redux | reactredux | [C#] | Web/MVC/SPA |
+| MVC ASP.NET Core with Vue.js | vue | [C#] | Web/MVC/SPA |
+
+To create a new project using one of the SPA templates, include the **Short Name** of the template in the `dotnet new` command. The following command creates an Angular application with ASP.NET Core MVC configured for the server side:
+
+```console
+dotnet new angular
+```
+
+
+
+### Set the runtime configuration mode
+
+Two primary runtime configuration modes exist:
+* **Development**:
+ * Includes source maps to ease debugging.
+ * Doesn't optimize the client-side code for performance.
+* **Production**:
+ * Excludes source maps.
+ * Optimizes the client-side code via bundling & minification.
+
+ASP.NET Core uses an environment variable named `ASPNETCORE_ENVIRONMENT` to store the configuration mode. See **[Setting the environment](xref:fundamentals/environments#setting-the-environment)** for more information.
+
+### Running with .NET Core CLI
+
+Restore the required NuGet and npm packages by running the following command at the project root:
+
+```console
+dotnet restore && npm i
+```
+
+Build and run the application:
+
+```console
+dotnet run
+```
+
+The application starts on localhost according to the [runtime configuration mode](#runtime-config-mode). Navigating to `http://localhost:5000` in the browser displays the landing page.
+
+### Running with Visual Studio 2017
+
+Open the *.csproj* file generated by the `dotnet new` command. The required NuGet and npm packages are restored automatically upon project open. This restoration process may take up to a few minutes, and the application is ready to run when it completes. Click the green run button or press `Ctrl + F5`, and the browser opens to the application's landing page. The application runs on localhost according to the [runtime configuration mode](#runtime-config-mode).
+
+
+
+## Testing the app
+
+SpaServices templates are pre-configured to run client-side tests using [Karma](https://karma-runner.github.io/1.0/index.html) and [Jasmine](https://jasmine.github.io/). Jasmine is a popular unit testing framework for JavaScript, whereas Karma is a test runner for those tests. Karma is configured to work with the [Webpack Dev Middleware](#webpack-dev-middleware) such that you don’t have to stop and run the test every time changes are made. Whether it's the code running against the test case or the test case itself, the test runs automatically.
+
+Using the Angular application as an example, two Jasmine test cases are already provided for the `CounterComponent` in the *counter.component.spec.ts* file:
+
+[!code-typescript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/counter/counter.component.spec.ts?range=15-28)]
+
+Open the command prompt at the project root, and run the following command:
+
+```console
+npm test
+```
+
+The script launches the Karma test runner, which reads the settings defined in the *karma.conf.js* file. Among other settings, the *karma.conf.js* identifies the test files to be executed via its `files` array:
+
+[!code-javascript[Main](../client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/karma.conf.js?range=4-5,8-11)]
+
+
+
+## Publishing the application
+
+Combining the generated client-side assets and the published ASP.NET Core artifacts into a ready-to-deploy package can be cumbersome. Thankfully, SpaServices orchestrates that entire publication process with a custom MSBuild target named `RunWebpack`:
+
+[!code-xml[Main](../client-side/spa-services/sample/SpaServicesSampleApp/SpaServicesSampleApp.csproj?range=31-45)]
+
+The MSBuild target has the following responsibilities:
+1. Restore the npm packages
+1. Create a production-grade build of the third-party, client-side assets
+1. Create a production-grade build of the custom client-side assets
+1. Copy the Webpack-generated assets to the publish folder
+
+The MSBuild target is invoked when running:
+
+```console
+dotnet publish -c Release
+```
+
+## Additional resources
+
+* [Angular Docs](https://angular.io/docs)
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/_static/global_variable.png b/aspnetcore/client-side/spa-services/_static/global_variable.png
new file mode 100644
index 000000000000..18f68f29b49d
Binary files /dev/null and b/aspnetcore/client-side/spa-services/_static/global_variable.png differ
diff --git a/aspnetcore/client-side/spa-services/_static/hmr_connected.png b/aspnetcore/client-side/spa-services/_static/hmr_connected.png
new file mode 100644
index 000000000000..346b80e6888e
Binary files /dev/null and b/aspnetcore/client-side/spa-services/_static/hmr_connected.png differ
diff --git a/aspnetcore/client-side/spa-services/_static/tag_helper_intellisense.png b/aspnetcore/client-side/spa-services/_static/tag_helper_intellisense.png
new file mode 100644
index 000000000000..9f3d842dce68
Binary files /dev/null and b/aspnetcore/client-side/spa-services/_static/tag_helper_intellisense.png differ
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/.gitignore b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/.gitignore
new file mode 100644
index 000000000000..d261e7c72984
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/.gitignore
@@ -0,0 +1,237 @@
+/Properties/launchSettings.json
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+bin/
+Bin/
+obj/
+Obj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+/wwwroot/dist/
+/ClientApp/dist/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directory
+AppPackages/
+BundleArtifacts/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+/node_modules
+
+/yarn.lock
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.client.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.client.ts
new file mode 100644
index 000000000000..ee77812262e4
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.client.ts
@@ -0,0 +1,21 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+import { HttpModule } from '@angular/http';
+import { sharedConfig } from './app.module.shared';
+
+@NgModule({
+ bootstrap: sharedConfig.bootstrap,
+ declarations: sharedConfig.declarations,
+ imports: [
+ BrowserModule,
+ FormsModule,
+ HttpModule,
+ ...sharedConfig.imports
+ ],
+ providers: [
+ { provide: 'ORIGIN_URL', useValue: location.origin }
+ ]
+})
+export class AppModule {
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.server.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.server.ts
new file mode 100644
index 000000000000..f2b6eb4b37a8
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.server.ts
@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { ServerModule } from '@angular/platform-server';
+import { sharedConfig } from './app.module.shared';
+
+@NgModule({
+ bootstrap: sharedConfig.bootstrap,
+ declarations: sharedConfig.declarations,
+ imports: [
+ ServerModule,
+ ...sharedConfig.imports
+ ]
+})
+export class AppModule {
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.shared.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.shared.ts
new file mode 100644
index 000000000000..570e638f6327
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/app.module.shared.ts
@@ -0,0 +1,34 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { AppComponent } from './components/app/app.component'
+import { NavMenuComponent } from './components/navmenu/navmenu.component';
+import { HomeComponent } from './components/home/home.component';
+import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
+import { CounterComponent } from './components/counter/counter.component';
+import { BlogComponent } from './components/blog/blog.component';
+import { PostComponent } from './components/post/post.component';
+
+export const sharedConfig: NgModule = {
+ bootstrap: [ AppComponent ],
+ declarations: [
+ AppComponent,
+ NavMenuComponent,
+ CounterComponent,
+ FetchDataComponent,
+ HomeComponent,
+ BlogComponent,
+ PostComponent
+ ],
+ imports: [
+ RouterModule.forRoot([
+ { path: '', redirectTo: 'home', pathMatch: 'full' },
+ { path: 'home', component: HomeComponent },
+ { path: 'counter', component: CounterComponent },
+ { path: 'fetch-data', component: FetchDataComponent },
+ { path: 'blog', component: BlogComponent },
+ { path: 'blog/:id', component: PostComponent },
+ { path: '**', redirectTo: 'home' }
+ ])
+ ]
+};
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.css b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.css
new file mode 100644
index 000000000000..63926006af63
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.css
@@ -0,0 +1,6 @@
+@media (max-width: 767px) {
+ /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
+ .body-content {
+ padding-top: 50px;
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.html b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.html
new file mode 100644
index 000000000000..f208d1e83e9f
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.ts
new file mode 100644
index 000000000000..b20a1aed2b73
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/app/app.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/blog/blog.component.html b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/blog/blog.component.html
new file mode 100644
index 000000000000..9655e04950c5
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/blog/blog.component.html
@@ -0,0 +1,18 @@
+
Client-side navigation. For example, click Counter then Back to return here.
+
Server-side prerendering. For faster initial loading and improved SEO, your Angular app is prerendered on the server. The resulting HTML is then transferred to the browser where a client-side copy of the app takes over.
+
Webpack dev middleware. In development mode, there's no need to run the webpack build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.
+
Hot module replacement. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, your Angular app will be rebuilt and a new instance injected is into the page.
+
Efficient production builds. In production mode, development-time features are disabled, and the webpack build tool produces minified static CSS and JavaScript files.
+
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/home/home.component.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/home/home.component.ts
new file mode 100644
index 000000000000..81846cee82aa
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/home/home.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'home',
+ templateUrl: './home.component.html'
+})
+export class HomeComponent {
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.css b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.css
new file mode 100644
index 000000000000..e15c61289531
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.css
@@ -0,0 +1,59 @@
+li .glyphicon {
+ margin-right: 10px;
+}
+
+/* Highlighting rules for nav menu items */
+li.link-active a,
+li.link-active a:hover,
+li.link-active a:focus {
+ background-color: #4189C7;
+ color: white;
+}
+
+/* Keep the nav menu independent of scrolling and on top of other items */
+.main-nav {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1;
+}
+
+@media (min-width: 768px) {
+ /* On small screens, convert the nav menu to a vertical sidebar */
+ .main-nav {
+ height: 100%;
+ width: calc(25% - 20px);
+ }
+ .navbar {
+ border-radius: 0px;
+ border-width: 0px;
+ height: 100%;
+ }
+ .navbar-header {
+ float: none;
+ }
+ .navbar-collapse {
+ border-top: 1px solid #444;
+ padding: 0px;
+ }
+ .navbar ul {
+ float: none;
+ }
+ .navbar li {
+ float: none;
+ font-size: 15px;
+ margin: 6px;
+ }
+ .navbar li a {
+ padding: 10px 16px;
+ border-radius: 4px;
+ }
+ .navbar a {
+ /* If a menu item's text is too long, truncate it */
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.html b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.html
new file mode 100644
index 000000000000..e6501d251ef1
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/navmenu/navmenu.component.html
@@ -0,0 +1,38 @@
+
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/post/post.component.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/post/post.component.ts
new file mode 100644
index 000000000000..103a14e0af15
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/app/components/post/post.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { Http } from '@angular/http';
+import { ActivatedRoute, Params } from '@angular/router';
+import { Location } from '@angular/common';
+import 'rxjs/add/operator/switchMap';
+
+@Component({
+ selector: 'post',
+ templateUrl: './post.component.html'
+})
+export class PostComponent {
+ public posts: Post[];
+
+ constructor(private route: ActivatedRoute, private location: Location, private http: Http) {
+ this.route.params
+ .switchMap((params: Params) => http.get('/api/blogs/' + params['id'] + '/posts'))
+ .subscribe(result => this.posts = result.json() as Post[]);
+ }
+}
+
+interface Post {
+ postId: number;
+ title: string;
+ author: string;
+ link: string;
+}
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-client.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-client.ts
new file mode 100644
index 000000000000..45c471c417b0
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-client.ts
@@ -0,0 +1,22 @@
+import 'reflect-metadata';
+import 'zone.js';
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { AppModule } from './app/app.module.client';
+
+if (module['hot']) {
+ module['hot'].accept();
+ module['hot'].dispose(() => {
+ // Before restarting the app, we create a new root element and dispose the old one
+ const oldRootElem = document.querySelector('app');
+ const newRootElem = document.createElement('app');
+ oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
+ modulePromise.then(appModule => appModule.destroy());
+ });
+} else {
+ enableProdMode();
+}
+
+// Note: @ng-tools/webpack looks for the following expression when performing production
+// builds. Don't change how this line looks, otherwise you may break tree-shaking.
+const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts
new file mode 100644
index 000000000000..144ee7b0ea54
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/boot-server.ts
@@ -0,0 +1,80 @@
+import 'reflect-metadata';
+import 'zone.js';
+import 'rxjs/add/operator/first';
+import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core';
+import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server';
+import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
+import { AppModule } from './app/app.module.server';
+
+enableProdMode();
+
+export default createServerRenderer(params => {
+ const providers = [
+ { provide: INITIAL_CONFIG, useValue: { document: '', url: params.url } },
+ { provide: 'ORIGIN_URL', useValue: params.origin }
+ ];
+
+ return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
+ const appRef = moduleRef.injector.get(ApplicationRef);
+ const state = moduleRef.injector.get(PlatformState);
+ const zone = moduleRef.injector.get(NgZone);
+
+ return new Promise((resolve, reject) => {
+ zone.onError.subscribe(errorInfo => reject(errorInfo));
+ appRef.isStable.first(isStable => isStable).subscribe(() => {
+ // Because 'onStable' fires before 'onError', we have to delay slightly before
+ // completing the request in case there's an error to report
+ setImmediate(() => {
+ resolve({
+ html: state.renderToString()
+ });
+ moduleRef.destroy();
+ });
+ });
+ });
+
+ // Example of accessing arguments passed from the Tag Helper
+ /*
+ return new Promise((resolve, reject) => {
+ const result = `
Hello, ${params.data.userName}
`;
+
+ zone.onError.subscribe(errorInfo => reject(errorInfo));
+ appRef.isStable.first(isStable => isStable).subscribe(() => {
+ // Because 'onStable' fires before 'onError', we have to delay slightly before
+ // completing the request in case there's an error to report
+ setImmediate(() => {
+ resolve({
+ html: result
+ });
+ moduleRef.destroy();
+ });
+ });
+ });
+ */
+
+ // Example of attaching property to browser's "window" object
+ /*
+ return new Promise((resolve, reject) => {
+ const result = `
Hello, ${params.data.userName}
`;
+
+ zone.onError.subscribe(errorInfo => reject(errorInfo));
+ appRef.isStable.first(isStable => isStable).subscribe(() => {
+ // Because 'onStable' fires before 'onError', we have to delay slightly before
+ // completing the request in case there's an error to report
+ setImmediate(() => {
+ resolve({
+ html: result,
+ globals: {
+ postList: [
+ 'Introduction to ASP.NET Core',
+ 'Making apps with Angular and ASP.NET Core'
+ ]
+ }
+ });
+ moduleRef.destroy();
+ });
+ });
+ });
+ */
+ });
+});
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/boot-tests.ts b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/boot-tests.ts
new file mode 100644
index 000000000000..fe3591e904b5
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/boot-tests.ts
@@ -0,0 +1,33 @@
+// Load required polyfills and testing libraries
+import 'reflect-metadata';
+import 'zone.js';
+import 'zone.js/dist/long-stack-trace-zone';
+import 'zone.js/dist/proxy.js';
+import 'zone.js/dist/sync-test';
+import 'zone.js/dist/jasmine-patch';
+import 'zone.js/dist/async-test';
+import 'zone.js/dist/fake-async-test';
+import * as testing from '@angular/core/testing';
+import * as testingBrowser from '@angular/platform-browser-dynamic/testing';
+
+// There's no typing for the `__karma__` variable. Just declare it as any
+declare var __karma__: any;
+declare var require: any;
+
+// Prevent Karma from running prematurely
+__karma__.loaded = function () {};
+
+// First, initialize the Angular testing environment
+testing.getTestBed().initTestEnvironment(
+ testingBrowser.BrowserDynamicTestingModule,
+ testingBrowser.platformBrowserDynamicTesting()
+);
+
+// Then we find all the tests
+const context = require.context('../', true, /\.spec\.ts$/);
+
+// And load the modules
+context.keys().map(context);
+
+// Finally, start Karma to run the tests
+__karma__.start();
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/karma.conf.js b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/karma.conf.js
new file mode 100644
index 000000000000..c7f03d49a32a
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/ClientApp/test/karma.conf.js
@@ -0,0 +1,26 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/0.13/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '.',
+ frameworks: ['jasmine'],
+ files: [
+ '../../wwwroot/dist/vendor.js',
+ './boot-tests.ts'
+ ],
+ preprocessors: {
+ './boot-tests.ts': ['webpack']
+ },
+ reporters: ['progress'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ mime: { 'application/javascript': ['ts','tsx'] },
+ singleRun: false,
+ webpack: require('../../webpack.config.js')().filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
+ webpackMiddleware: { stats: 'errors-only' }
+ });
+};
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/BlogsController.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/BlogsController.cs
new file mode 100644
index 000000000000..cab55b075c4c
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/BlogsController.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using SpaServicesSampleApp.Data;
+
+namespace SpaServicesSampleApp.Controllers
+{
+ [Route("api/[controller]")]
+ public class BlogsController : Controller
+ {
+ [HttpGet]
+ public IActionResult Get()
+ {
+ var blogs = SampleData.Blogs();
+
+ return Ok(blogs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/HomeController.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/HomeController.cs
new file mode 100644
index 000000000000..172ab09e3235
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/HomeController.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace SpaServicesSampleApp.Controllers
+{
+ public class HomeController : Controller
+ {
+ public IActionResult Index() => View();
+
+ public IActionResult Error() => View();
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/PostsController.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/PostsController.cs
new file mode 100644
index 000000000000..a3088fbe5753
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/PostsController.cs
@@ -0,0 +1,18 @@
+using System.Linq;
+using Microsoft.AspNetCore.Mvc;
+using SpaServicesSampleApp.Data;
+
+namespace SpaServicesSampleApp.Controllers
+{
+ [Route("api/blogs/{blogId}/posts")]
+ public class PostsController : Controller
+ {
+ [HttpGet()]
+ public IActionResult Get(int blogId)
+ {
+ var posts = SampleData.Posts().Where(p => p.BlogId == blogId);
+
+ return Ok(posts);
+ }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/SampleDataController.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/SampleDataController.cs
new file mode 100644
index 000000000000..d8e8b53ae349
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Controllers/SampleDataController.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc;
+
+namespace SpaServicesSampleApp.Controllers
+{
+ [Route("api/[controller]")]
+ public class SampleDataController : Controller
+ {
+ private static string[] Summaries = new[]
+ {
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+ };
+
+ [HttpGet("[action]")]
+ public IEnumerable WeatherForecasts()
+ {
+ var rng = new Random();
+ return Enumerable.Range(1, 5).Select(index => new WeatherForecast
+ {
+ DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
+ TemperatureC = rng.Next(-20, 55),
+ Summary = Summaries[rng.Next(Summaries.Length)]
+ });
+ }
+
+ public class WeatherForecast
+ {
+ public string DateFormatted { get; set; }
+ public int TemperatureC { get; set; }
+ public string Summary { get; set; }
+
+ public int TemperatureF
+ {
+ get
+ {
+ return 32 + (int)(TemperatureC / 0.5556);
+ }
+ }
+ }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Data/SampleData.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Data/SampleData.cs
new file mode 100644
index 000000000000..f1806288098c
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Data/SampleData.cs
@@ -0,0 +1,28 @@
+using SpaServicesSampleApp.Models;
+using System.Collections.Generic;
+
+namespace SpaServicesSampleApp.Data
+{
+ public static class SampleData
+ {
+ public static IEnumerable Blogs()
+ {
+ return new List()
+ {
+ new Blog{ BlogId = 1, Url = "http://blog.stevensanderson.com/", Title = "Steve Sanderson's Blog" },
+ new Blog{ BlogId = 2, Url = "http://fiyazhasan.me", Title = "Classroom of Fizz" }
+ };
+ }
+
+ public static IEnumerable Posts()
+ {
+ return new List
+ {
+ new Post(){ PostId=1, Title="ASP.NET Core + Angular 2 template for Visual Studio", Author="Steve Sanderson", Link="http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/", BlogId=1},
+ new Post(){ PostId=2, Title="Angular 2, React, and Knockout apps on ASP.NET Core", Author="Steve Sanderson", Link="http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/", BlogId=1},
+ new Post(){ PostId=3, Title="Building Custom Formatters for .Net Core (Yaml Formatters)", Author="Fiyaz Hasan", Link="http://www.fiyazhasan.me/building-custom-formatters-for-net-core-yaml-formatters/", BlogId=2},
+ new Post(){ PostId=4, Title="Preventing XSRF in AngularJS Apps with ASP.NET CORE Anti-Forgery Middleware", Author="Fiyaz Hasan", Link="http://www.fiyazhasan.me/angularjs-anti-forgery-with-asp-net-core/", BlogId=2}
+ };
+ }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Blog.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Blog.cs
new file mode 100644
index 000000000000..19d54f7bcfcc
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Blog.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace SpaServicesSampleApp.Models
+{
+ public class Blog
+ {
+ public int BlogId { get; set; }
+ public string Url { get; set; }
+ public string Title { get; set; }
+
+ public List Posts { get; set; }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Post.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Post.cs
new file mode 100644
index 000000000000..94588bcbb2bd
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Models/Post.cs
@@ -0,0 +1,11 @@
+namespace SpaServicesSampleApp.Models
+{
+ public class Post
+ {
+ public int PostId { get; set; }
+ public string Title { get; set; }
+ public string Link { get; set; }
+ public string Author { get; set; }
+ public int BlogId { get; set; }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Program.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Program.cs
new file mode 100644
index 000000000000..13e846972e82
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Program.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+
+namespace SpaServicesSampleApp
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/SpaServicesSampleApp.csproj b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/SpaServicesSampleApp.csproj
new file mode 100644
index 000000000000..f1d66a25bf33
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/SpaServicesSampleApp.csproj
@@ -0,0 +1,46 @@
+
+
+ netcoreapp1.1
+ true
+ false
+ portable-net45+win8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ package.json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(DistFiles.Identity)
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Startup.cs b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Startup.cs
new file mode 100644
index 000000000000..c668ee825706
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Startup.cs
@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace SpaServicesSampleApp
+{
+ public class Startup
+ {
+ public Startup(IHostingEnvironment env)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(env.ContentRootPath)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
+ .AddEnvironmentVariables();
+ Configuration = builder.Build();
+ }
+
+ public IConfigurationRoot Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Add framework services.
+ services.AddMvc();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
+ {
+ loggerFactory.AddConsole(Configuration.GetSection("Logging"));
+ loggerFactory.AddDebug();
+
+ #region webpack-middleware-registration
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ app.UseWebpackDevMiddleware();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Home/Error");
+ }
+
+ // Call UseWebpackDevMiddleware before UseStaticFiles
+ app.UseStaticFiles();
+ #endregion
+
+ #region mvc-routing-table
+ app.UseMvc(routes =>
+ {
+ routes.MapRoute(
+ name: "default",
+ template: "{controller=Home}/{action=Index}/{id?}");
+
+ routes.MapSpaFallbackRoute(
+ name: "spa-fallback",
+ defaults: new { controller = "Home", action = "Index" });
+ });
+ #endregion
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Home/Index.cshtml b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Home/Index.cshtml
new file mode 100644
index 000000000000..2b7be1ccf65b
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Home/Index.cshtml
@@ -0,0 +1,18 @@
+@{
+ ViewData["Title"] = "Home Page";
+}
+
+Loading...
+
+@* Example of asp-prerender-data Tag Helper passing data to server-side JavaScript *@
+@*
+Loading...
+*@
+
+
+@section scripts {
+
+}
\ No newline at end of file
diff --git a/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Shared/Error.cshtml b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Shared/Error.cshtml
new file mode 100644
index 000000000000..473b35d6caa8
--- /dev/null
+++ b/aspnetcore/client-side/spa-services/sample/SpaServicesSampleApp/Views/Shared/Error.cshtml
@@ -0,0 +1,6 @@
+@{
+ ViewData["Title"] = "Error";
+}
+
+