17 | Swapping to Development environment will display more detailed information about the error that occurred.
18 |
19 |
20 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
21 |
22 |
--------------------------------------------------------------------------------
/ClientApp/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '.',
7 | frameworks: ['jasmine'],
8 | files: [
9 | '../../wwwroot/dist/vendor.js',
10 | './boot-tests.ts'
11 | ],
12 | preprocessors: {
13 | './boot-tests.ts': ['webpack']
14 | },
15 | reporters: ['progress'],
16 | port: 9876,
17 | colors: true,
18 | logLevel: config.LOG_INFO,
19 | autoWatch: true,
20 | browsers: ['Chrome'],
21 | mime: { 'application/javascript': ['ts','tsx'] },
22 | singleRun: false,
23 | webpack: require('../../webpack.config.js')().filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
24 | webpackMiddleware: { stats: 'errors-only' }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/ClientApp/boot.browser.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 | import 'zone.js';
3 | import 'bootstrap';
4 | import { enableProdMode } from '@angular/core';
5 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
6 | import { AppModule } from './app/app.module.browser';
7 |
8 | if (module.hot) {
9 | module.hot.accept();
10 | module.hot.dispose(() => {
11 | // Before restarting the app, we create a new root element and dispose the old one
12 | const oldRootElem = document.querySelector('app');
13 | const newRootElem = document.createElement('app');
14 | oldRootElem!.parentNode!.insertBefore(newRootElem, oldRootElem);
15 | modulePromise.then(appModule => appModule.destroy());
16 | });
17 | } else {
18 | enableProdMode();
19 | }
20 |
21 | // Note: @ng-tools/webpack looks for the following expression when performing production
22 | // builds. Don't change how this line looks, otherwise you may break tree-shaking.
23 | const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);
24 |
--------------------------------------------------------------------------------
/ClientApp/app/interceptors/authentication.httpInterceptor.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs/Observable';
2 | import 'rxjs/add/observable/fromPromise';
3 | import 'rxjs/add/operator/switchMap';
4 |
5 | import { Injectable } from '@angular/core';
6 | import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
7 | import { AuthenticationService } from '../services/authentication.service';
8 |
9 | @Injectable()
10 | export class AuthenticationHttpInterceptor implements HttpInterceptor {
11 |
12 | constructor(private authenticationService: AuthenticationService) { }
13 |
14 | intercept(req: HttpRequest, next: HttpHandler): Observable> {
15 |
16 | return Observable.fromPromise(this.authenticationService.getAuthenticationToken())
17 | .switchMap(token => {
18 | req = req.clone({
19 | setHeaders: {
20 | Authorization: `Bearer ${token}`
21 | }
22 | });
23 | return next.handle(req);
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Xavier Hahn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ClientApp/test/boot-tests.ts:
--------------------------------------------------------------------------------
1 | // Load required polyfills and testing libraries
2 | import 'reflect-metadata';
3 | import 'zone.js';
4 | import 'zone.js/dist/long-stack-trace-zone';
5 | import 'zone.js/dist/proxy.js';
6 | import 'zone.js/dist/sync-test';
7 | import 'zone.js/dist/jasmine-patch';
8 | import 'zone.js/dist/async-test';
9 | import 'zone.js/dist/fake-async-test';
10 | import * as testing from '@angular/core/testing';
11 | import * as testingBrowser from '@angular/platform-browser-dynamic/testing';
12 |
13 | // There's no typing for the `__karma__` variable. Just declare it as any
14 | declare var __karma__: any;
15 | declare var require: any;
16 |
17 | // Prevent Karma from running prematurely
18 | __karma__.loaded = function () {};
19 |
20 | // First, initialize the Angular testing environment
21 | testing.getTestBed().initTestEnvironment(
22 | testingBrowser.BrowserDynamicTestingModule,
23 | testingBrowser.platformBrowserDynamicTesting()
24 | );
25 |
26 | // Then we find all the tests
27 | const context = require.context('../', true, /\.spec\.ts$/);
28 |
29 | // And load the modules
30 | context.keys().map(context);
31 |
32 | // Finally, start Karma to run the tests
33 | __karma__.start();
34 |
--------------------------------------------------------------------------------
/msal-netcore-angular.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27004.2005
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "msal-netcore-angular", "msal-netcore-angular.csproj", "{679AC8AD-BDCD-4365-97D4-023E87A3C8DD}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {679AC8AD-BDCD-4365-97D4-023E87A3C8DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {679AC8AD-BDCD-4365-97D4-023E87A3C8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {679AC8AD-BDCD-4365-97D4-023E87A3C8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {679AC8AD-BDCD-4365-97D4-023E87A3C8DD}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {1F262BB8-4D88-4468-A314-9084FDE598F0}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/ClientApp/app/components/counter/counter.component.spec.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { assert } from 'chai';
3 | import { CounterComponent } from './counter.component';
4 | import { TestBed, async, ComponentFixture } from '@angular/core/testing';
5 |
6 | let fixture: ComponentFixture;
7 |
8 | describe('Counter component', () => {
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({ declarations: [CounterComponent] });
11 | fixture = TestBed.createComponent(CounterComponent);
12 | fixture.detectChanges();
13 | });
14 |
15 | it('should display a title', async(() => {
16 | const titleText = fixture.nativeElement.querySelector('h1').textContent;
17 | expect(titleText).toEqual('Counter');
18 | }));
19 |
20 | it('should start with count 0, then increments by 1 when clicked', async(() => {
21 | const countElement = fixture.nativeElement.querySelector('strong');
22 | expect(countElement.textContent).toEqual('0');
23 |
24 | const incrementButton = fixture.nativeElement.querySelector('button');
25 | incrementButton.click();
26 | fixture.detectChanges();
27 | expect(countElement.textContent).toEqual('1');
28 | }));
29 | });
30 |
--------------------------------------------------------------------------------
/ClientApp/app/components/navmenu/navmenu.component.css:
--------------------------------------------------------------------------------
1 | li .glyphicon {
2 | margin-right: 10px;
3 | }
4 |
5 | /* Highlighting rules for nav menu items */
6 | li.link-active a,
7 | li.link-active a:hover,
8 | li.link-active a:focus {
9 | background-color: #4189C7;
10 | color: white;
11 | }
12 |
13 | /* Keep the nav menu independent of scrolling and on top of other items */
14 | .main-nav {
15 | position: fixed;
16 | top: 0;
17 | left: 0;
18 | right: 0;
19 | z-index: 1;
20 | }
21 |
22 | @media (min-width: 768px) {
23 | /* On small screens, convert the nav menu to a vertical sidebar */
24 | .main-nav {
25 | height: 100%;
26 | width: calc(25% - 20px);
27 | }
28 | .navbar {
29 | border-radius: 0px;
30 | border-width: 0px;
31 | height: 100%;
32 | }
33 | .navbar-header {
34 | float: none;
35 | }
36 | .navbar-collapse {
37 | border-top: 1px solid #444;
38 | padding: 0px;
39 | }
40 | .navbar p {
41 | color: white;
42 | }
43 | .navbar ul {
44 | float: none;
45 | }
46 | .navbar li {
47 | float: none;
48 | font-size: 15px;
49 | margin: 6px;
50 | }
51 | .navbar li a {
52 | padding: 10px 16px;
53 | border-radius: 4px;
54 | }
55 | .navbar a {
56 | /* If a menu item's text is too long, truncate it */
57 | width: 100%;
58 | white-space: nowrap;
59 | overflow: hidden;
60 | text-overflow: ellipsis;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Controllers/SampleDataController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | namespace msal_netcore_angular.Controllers
9 | {
10 | [Authorize]
11 | [Route("api/[controller]")]
12 | public class SampleDataController : Controller
13 | {
14 | private static string[] Summaries = new[]
15 | {
16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17 | };
18 |
19 | [HttpGet("[action]")]
20 | public IEnumerable WeatherForecasts()
21 | {
22 | var rng = new Random();
23 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
24 | {
25 | DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
26 | TemperatureC = rng.Next(-20, 55),
27 | Summary = Summaries[rng.Next(Summaries.Length)]
28 | });
29 | }
30 |
31 | public class WeatherForecast
32 | {
33 | public string DateFormatted { get; set; }
34 | public int TemperatureC { get; set; }
35 | public string Summary { get; set; }
36 |
37 | public int TemperatureF
38 | {
39 | get
40 | {
41 | return 32 + (int)(TemperatureC / 0.5556);
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ClientApp/app/app.module.shared.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
4 | import { FormsModule } from '@angular/forms';
5 | import { RouterModule } from '@angular/router';
6 |
7 | import { AppComponent } from './components/app/app.component';
8 | import { NavMenuComponent } from './components/navmenu/navmenu.component';
9 | import { HomeComponent } from './components/home/home.component';
10 | import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
11 | import { CounterComponent } from './components/counter/counter.component';
12 | import { AuthenticationService } from './services/authentication.service';
13 | import { AuthenticationHttpInterceptor } from './interceptors/authentication.httpInterceptor';
14 |
15 | @NgModule({
16 | declarations: [
17 | AppComponent,
18 | NavMenuComponent,
19 | CounterComponent,
20 | FetchDataComponent,
21 | HomeComponent
22 | ],
23 | imports: [
24 | CommonModule,
25 | HttpClientModule,
26 | FormsModule,
27 | RouterModule.forRoot([
28 | { path: '', component: HomeComponent },
29 | { path: 'home', component: HomeComponent },
30 | { path: 'counter', component: CounterComponent },
31 | { path: 'fetch-data', component: FetchDataComponent },
32 | { path: '**', redirectTo: 'home' }
33 | ]),
34 | ],
35 | providers: [
36 | AuthenticationService,
37 | {
38 | provide: HTTP_INTERCEPTORS,
39 | useClass: AuthenticationHttpInterceptor,
40 | multi: true
41 | }
42 | ]
43 | })
44 | export class AppModuleShared {
45 | }
46 |
--------------------------------------------------------------------------------
/ClientApp/app/components/home/home.component.html:
--------------------------------------------------------------------------------
1 |
Hello, world!
2 |
Welcome to your new single-page application, built with:
Client-side navigation. For example, click Counter then Back to return here.
12 |
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.
13 |
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.
14 |
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.
15 |
Efficient production builds. In production mode, development-time features are disabled, and the webpack build tool produces minified static CSS and JavaScript files.