Skip to content

Instantly share code, notes, and snippets.

@JaimeStill
Last active March 12, 2017 20:23
Show Gist options
  • Save JaimeStill/6e004d7c698c310e0055cb0403bfdffe to your computer and use it in GitHub Desktop.
Save JaimeStill/6e004d7c698c310e0055cb0403bfdffe to your computer and use it in GitHub Desktop.
Angular and ASP.NET Core Template / Workflow

Angular and ASP.NET Core Template Scaffolding and Workflow

Documentation in Progress

See on GitHub

This project was put together based off of the Angular projects from the generator-aspnetcore-spa generator. I wanted to understand how all of the components worked in that project template, and how to strip it down to a more bare starting point. A big reason for this was that I didn't want to be tied to using Bootstrap

Contents

Required Tools

Back to Top

Initial Layout Without Style Framework

Back to Top

  • .vscode
    • launch.json
    • tasks.json
  • Controllers
    • DataController.cs
    • HomeController.cs
  • Models
    • Extensions
      • WeatherExtensions.cs
    • WeatherForecast.cs
  • src
    • app
      • components
        • app
          • app.component.css
          • app.component.html
          • app.component.ts
        • home
          • home.component.html
          • home.component.ts
      • app.module.ts
    • boot-client.ts
    • boot-server.ts
  • Views
    • Home
      • Index.cshtml
    • Shared
      • _Layout.cshtml
      • Error.cshtml
    • _ViewImports.cshtml
    • _ViewStart.cshtml
  • wwwroot
  • .gitignore
  • appsettings.json
  • CoreTemplate.csproj
  • global.json
  • package.json
  • Program.cs
  • Startup.cs
  • tsconfig.json
  • web.config
  • webpack.config.js
  • webpack.config.vendor.js

Basic Steps

Back to Top

mkdir {template} && cd {template}
npm init -y
code .

Install Dependencies

Back to Top

Using npm, install the dependencies listed in package.json as follows

npm install --save {dependency}

The only sections that need to be kept are name, version, dependencies, and scripts.

Note the scripts section is covered later when talking about build tools

package.json

{
  "name": "core-template",
  "version": "1.0.0",
  "dependencies": {
    "@angular/common": "^2.4.9",
    "@angular/compiler": "^2.4.9",
    "@angular/core": "^2.4.9",
    "@angular/forms": "^2.4.9",
    "@angular/http": "^2.4.9",
    "@angular/platform-browser": "^2.4.9",
    "@angular/platform-browser-dynamic": "^2.4.9",
    "@angular/platform-server": "^2.4.9",
    "@angular/router": "^3.4.9",
    "@types/node": "^7.0.8",
    "angular2-platform-node": "^2.1.0-rc.1",
    "angular2-template-loader": "^0.6.2",
    "angular2-universal": "^2.1.0-rc.1",
    "angular2-universal-patch": "^0.2.1",
    "angular2-universal-polyfills": "^2.1.0-rc.1",
    "aspnet-prerendering": "^2.0.3",
    "aspnet-webpack": "^1.0.28",
    "awesome-typescript-loader": "^3.1.2",
    "css": "^2.2.1",
    "css-loader": "^0.27.1",
    "es6-shim": "^0.35.3",
    "event-source-polyfill": "0.0.9",
    "expose-loader": "^0.7.3",
    "extract-text-webpack-plugin": "^2.1.0",
    "file-loader": "^0.10.1",
    "html-loader": "^0.4.5",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^3.1.1",
    "json-loader": "^0.5.4",
    "preboot": "^4.5.2",
    "raw-loader": "^0.5.1",
    "rxjs": "^5.2.0",
    "style-loader": "^0.13.2",
    "to-string-loader": "^1.1.5",
    "typescript": "^2.2.1",
    "url-loader": "^0.5.8",
    "webpack": "^2.2.1",
    "webpack-hot-middleware": "^2.17.1",
    "webpack-merge": "^4.0.0",
    "zone.js": "^0.8.0"
  },
  "scripts": {
    "build": "npm install && npm run webpack",
    "webpack": "webpack --config webpack.config.vendor.js && webpack"
  }
}

Configuration

Back to Top

Back to Top

Specifies files that Git should specifically ignore. Pulled from the aspnetcore-spa generator template

/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/**

# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235
!/wwwroot/dist/_placeholder.txt
!/ClientApp/dist/_placeholder.txt


# 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

# DNX
project.lock.json
artifacts/

*_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

# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235
/node_modules/**
!/node_modules/_placeholder.txt

/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/

Back to Top

Specifies the version of the .Net Core SDK to use for this project

{
  "sdk" { "version": "1.1.1" }
}

Back to Top

Provides various application configuration settings

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

Back to Top

The presence of tsconfig.json indicates the root of a TypeScript projects. Specifies the root files and the compiler options required to compile the project

{
    "compilerOptions": {
        "moduleResolution": "node",
        "target": "es5",
        "sourceMap": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "skipDefaultLibCheck": true,
        "lib": [ "es6", "dom" ],
        "types": [ "node" ]
    },
    "exclude": [ "bin", "node_modules" ],
    "atom": { "rewriteTsconfig": false }
}

Back to Top

Configures the ASP.NET Core Module

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
        </handlers>
        <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
    </system.webServer>
</configuration>

Back to Top

Specifies the MSBuild configuration for the project

<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>netcoreapp1.1</TargetFramework>
        <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
        <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
        <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
        <PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
        <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
        <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
    </ItemGroup>
    <ItemGroup>
        <!-- Files not to show in IDE -->
        <None Remove="yarn.lock" />

        <!-- Files not to publish (node that the 'dist' subfolders are re-added below) -->
        <Content Remove="src\**" />
    </ItemGroup>
    <Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
        <Exec Command="npm install" />
        <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
        <Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />

        <!-- Include the newly-built files in the publish output -->
        <ItemGroup>
            <DistFiles Include="wwwroot\dist\**; src\dist\**" />
            <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
                <RelativePath>%(DistFiles.Identity)</RelativePath>
                <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
            </ResolvedFileToPublish>
        </ItemGroup>
    </Target>
</Project>

ASP.NET Core Setup

Back to Top

In the above configuration section, the following files were specifically relevant to .NET Core:

  • appsettings.json
  • global.json
  • web.config
  • CoreTemplate.csproj

The file definitions that follow represent the starting point for the ASP.NET Core aspect of the project template

Back to Top

Application entry point. Builds hosting configuration using WebHostBuilder

using System.IO;
using Microsoft.AspNetCore.Hosting;

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

            host.Run();
        }
    }
}

Back to Top

The Startup class configures the request pipeline that handles all requests made to the application

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CoreTemplate
{
    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; }

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

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

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
                    HotModuleReplacement = true
                });
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");

                routes.MapSpaFallbackRoute(
                    name: "spa-fallback",
                    defaults: new { controller = "Home", action = "Index" });
            });
        }
    }
}

Although WeatherForecast class, WeatherExtensions class, and DataController API controller files are defined in the project, they are not actually used. They are purely an example for how to separate business logic from API calls and model definitions

Models/WeatherForecast.cs

Defines an example C# class model to be used in conjunction with an API call

namespace CoreTemplate.Models
{
    public class WeatherForecast
    {
        public string DateFormatted { get; set; }
        public int TemperatureC { get; set; }

        public string Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Models/Extensions/WeatherExtensions.cs

A static extensions class for business logic related to WeatherForecast

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CoreTemplate.Models.Extensions
{
    public static class WeatherExtensions
    {
        private static string[] Summaries = new[]
        {
            "Freezing",
            "Bracing",
            "Chilly",
            "Cool",
            "Mild",
            "Warm",
            "Balmy",
            "Hot",
            "Sweltering",
            "Scorching"
        };

        public static Task<IEnumerable<WeatherForecast>> GetWeatherForecasts(this Random rng)
        {
            return Task.Run(() =>
            {
                var model = 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)]
                });

                return model;
            });
        }
    }
}

DataController.cs

A Web API Controller. The use of the Route attribute specifies that all of the action methods in this controller can be reached at /api/Data. The HttpGet above the GetWeatherForecasts action method specifies that it is an HTTP GET request and that it can be reached at /api/Data/GetWeatherForecasts. Note that in this method, the collection is retrieved using the GetWeatherForecasts extension method defined in the WeatherExtensions class

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CoreTemplate.Models;
using CoreTemplate.Models.Extensions;
using Microsoft.AspNetCore.Mvc;

namespace CoreTemplate.Controllers
{
    [Route("api/[controller]")]
    public class DataController : Controller
    {
        [HttpGet("[action]")]
        public async Task<IEnumerable<WeatherForecast>> GetWeatherForecasts()
        {
            return await new Random().GetWeatherForecasts();
        }
    }
}

HomeController.cs

ASP.NET MVC Controller that provides an entry point to for the application

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace CoreTemplate.Controllers
{
    public class HomeController : Controller
    {
        public Task<IActionResult> Index()
        {
            return Task.Run(() =>
            {
                return (IActionResult)View();
            });
        }

        public Task<IActionResult> Error()
        {
            return Task.Run(() =>
            {
                return (IActionResult)View();
            });
        }
    }
}

Directives shared by many views may be specified in a common _ViewImports.cshtml file

@using CoreTemplate
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

By convention, the _ViewStart.cshtml file is located in the Views folder. The statements listed in _ViewStart.cshtml are run before every full view (not layouts, and not partial views)

@{
    Layout = "_Layout";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment