Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e65eceb

Browse files
committedJul 11, 2017
Make templates work with nonempty baseUrls (e.g., IIS virtual directories)
1 parent bb0727c commit e65eceb

File tree

31 files changed

+65
-44
lines changed

31 files changed

+65
-44
lines changed
 

‎templates/AngularSpa/ClientApp/app/app.module.browser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import { AppComponent } from './components/app/app.component';
1010
AppModuleShared
1111
],
1212
providers: [
13-
{ provide: 'ORIGIN_URL', useFactory: getOriginUrl }
13+
{ provide: 'BASE_URL', useFactory: getBaseUrl }
1414
]
1515
})
1616
export class AppModule {
1717
}
1818

19-
export function getOriginUrl() {
20-
return location.origin;
19+
export function getBaseUrl() {
20+
return document.getElementsByTagName('base')[0].href;
2121
}

‎templates/AngularSpa/ClientApp/app/components/fetchdata/fetchdata.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { Http } from '@angular/http';
88
export class FetchDataComponent {
99
public forecasts: WeatherForecast[];
1010

11-
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string) {
12-
http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => {
11+
constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
12+
http.get(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => {
1313
this.forecasts = result.json() as WeatherForecast[];
1414
}, error => console.error(error));
1515
}

‎templates/AngularSpa/ClientApp/boot.server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'reflect-metadata';
22
import 'zone.js';
33
import 'rxjs/add/operator/first';
4+
import { APP_BASE_HREF } from '@angular/common';
45
import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core';
56
import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server';
67
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
@@ -11,7 +12,8 @@ enableProdMode();
1112
export default createServerRenderer(params => {
1213
const providers = [
1314
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
14-
{ provide: 'ORIGIN_URL', useValue: params.origin }
15+
{ provide: APP_BASE_HREF, useValue: params.baseUrl },
16+
{ provide: 'BASE_URL', useValue: params.origin + params.baseUrl },
1517
];
1618

1719
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {

‎templates/AngularSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module.exports = (env) => {
1313
resolve: { extensions: [ '.js', '.ts' ] },
1414
output: {
1515
filename: '[name].js',
16-
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
16+
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
1717
},
1818
module: {
1919
rules: [

‎templates/AngularSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ module.exports = (env) => {
3636
]
3737
},
3838
output: {
39-
publicPath: '/dist/',
39+
publicPath: 'dist/',
4040
filename: '[name].js',
4141
library: '[name]_[hash]'
4242
},

‎templates/AureliaSpa/ClientApp/app/components/fetchdata/fetchdata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class Fetchdata {
66
public forecasts: WeatherForecast[];
77

88
constructor(http: HttpClient) {
9-
http.fetch('/api/SampleData/WeatherForecasts')
9+
http.fetch('api/SampleData/WeatherForecasts')
1010
.then(result => result.json() as Promise<WeatherForecast[]>)
1111
.then(data => {
1212
this.forecasts = data;

‎templates/AureliaSpa/ClientApp/boot.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'isomorphic-fetch';
22
import { Aurelia, PLATFORM } from 'aurelia-framework';
3+
import { HttpClient } from 'aurelia-fetch-client';
34
import 'bootstrap/dist/css/bootstrap.css';
45
import 'bootstrap';
56
declare const IS_DEV_BUILD: boolean; // The value is supplied by Webpack during the build
@@ -11,5 +12,10 @@ export function configure(aurelia: Aurelia) {
1112
aurelia.use.developmentLogging();
1213
}
1314

15+
new HttpClient().configure(config => {
16+
const baseUrl = document.getElementsByTagName('base')[0].href;
17+
config.withBaseUrl(baseUrl);
18+
});
19+
1420
aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app/components/app/app')));
1521
}

‎templates/AureliaSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module.exports = (env) => {
1414
},
1515
output: {
1616
path: path.resolve(bundleOutputDir),
17-
publicPath: '/dist/',
17+
publicPath: 'dist/',
1818
filename: '[name].js'
1919
},
2020
module: {

‎templates/AureliaSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = ({ prod } = {}) => {
3838
},
3939
output: {
4040
path: path.join(__dirname, 'wwwroot', 'dist'),
41-
publicPath: '/dist/',
41+
publicPath: 'dist/',
4242
filename: '[name].js',
4343
library: '[name]_[hash]',
4444
},

‎templates/KnockoutSpa/ClientApp/boot.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import * as ko from 'knockout';
44
import './webpack-component-loader';
55
import AppRootComponent from './components/app-root/app-root';
66
const createHistory = require('history').createBrowserHistory;
7+
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
8+
const basename = baseUrl.substring(0, baseUrl.length - 1); // History component needs no trailing slash
79

810
// Load and register the <app-root> component
911
ko.components.register('app-root', AppRootComponent);
1012

1113
// Tell Knockout to start up an instance of your application
12-
ko.applyBindings({ history: createHistory() });
14+
ko.applyBindings({ history: createHistory({ basename }), basename });
1315

1416
// Basic hot reloading support. Automatically reloads and restarts the Knockout app each time
1517
// you modify source files. This will not preserve any application state other than the URL.

‎templates/KnockoutSpa/ClientApp/components/app-root/app-root.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class='container-fluid'>
22
<div class='row'>
33
<div class='col-sm-3'>
4-
<nav-menu params='route: route'></nav-menu>
4+
<nav-menu params='router: router'></nav-menu>
55
</div>
66
<div class='col-sm-9' data-bind='component: { name: route().page, params: route }'></div>
77
</div>

‎templates/KnockoutSpa/ClientApp/components/app-root/app-root.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const routes: Route[] = [
1212

1313
class AppRootViewModel {
1414
public route: KnockoutObservable<Route>;
15-
private _router: Router;
15+
public router: Router;
1616

17-
constructor(params: { history: History.History }) {
17+
constructor(params: { history: History.History, basename: string }) {
1818
// Activate the client-side router
19-
this._router = new Router(params.history, routes)
20-
this.route = this._router.currentRoute;
19+
this.router = new Router(params.history, routes, params.basename);
20+
this.route = this.router.currentRoute;
2121

2222
// Load and register all the KO components needed to handle the routes
2323
// The optional 'bundle-loader?lazy!' prefix is a Webpack feature that causes the referenced modules
@@ -32,7 +32,7 @@ class AppRootViewModel {
3232
// To support hot module replacement, this method unregisters the router and KO components.
3333
// In production scenarios where hot module replacement is disabled, this would not be invoked.
3434
public dispose() {
35-
this._router.dispose();
35+
this.router.dispose();
3636

3737
// TODO: Need a better API for this
3838
Object.getOwnPropertyNames((<any>ko).components._allRegisteredComponents).forEach(componentName => {

‎templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class FetchDataViewModel {
1212
public forecasts = ko.observableArray<WeatherForecast>();
1313

1414
constructor() {
15-
fetch('/api/SampleData/WeatherForecasts')
15+
fetch('api/SampleData/WeatherForecasts')
1616
.then(response => response.json() as Promise<WeatherForecast[]>)
1717
.then(data => {
1818
this.forecasts(data);

‎templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
<div class='navbar-collapse collapse'>
1414
<ul class='nav navbar-nav'>
1515
<li>
16-
<a href='/' data-bind='css: { active: route().page === "home-page" }'>
16+
<a data-bind='attr: { href: router.link("/") }, css: { active: route().page === "home-page" }'>
1717
<span class='glyphicon glyphicon-home'></span> Home
1818
</a>
1919
</li>
2020
<li>
21-
<a href='/counter' data-bind='css: { active: route().page === "counter-example" }'>
21+
<a data-bind='attr: { href: router.link("/counter") }, css: { active: route().page === "counter-example" }'>
2222
<span class='glyphicon glyphicon-education'></span> Counter
2323
</a>
2424
</li>
2525
<li>
26-
<a href='/fetch-data' data-bind='css: { active: route().page === "fetch-data" }'>
26+
<a data-bind='attr: { href: router.link("/fetch-data") }, css: { active: route().page === "fetch-data" }'>
2727
<span class='glyphicon glyphicon-th-list'></span> Fetch data
2828
</a>
2929
</li>

‎templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import * as ko from 'knockout';
2-
import { Route } from '../../router';
2+
import { Route, Router } from '../../router';
33

44
interface NavMenuParams {
5-
route: KnockoutObservable<Route>;
5+
router: Router;
66
}
77

88
class NavMenuViewModel {
9+
public router: Router;
910
public route: KnockoutObservable<Route>;
1011

1112
constructor(params: NavMenuParams) {
1213
// This viewmodel doesn't do anything except pass through the 'route' parameter to the view.
1314
// You could remove this viewmodel entirely, and define 'nav-menu' as a template-only component.
1415
// But in most apps, you'll want some viewmodel logic to determine what navigation options appear.
15-
this.route = params.route;
16+
this.router = params.router;
17+
this.route = this.router.currentRoute;
1618
}
1719
}
1820

‎templates/KnockoutSpa/ClientApp/router.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class Router {
1616
private disposeHistory: () => void;
1717
private clickEventListener: EventListener;
1818

19-
constructor(history: History.History, routes: Route[]) {
19+
constructor(private history: History.History, routes: Route[], basename: string) {
2020
// Reset and configure Crossroads so it matches routes and updates this.currentRoute
2121
crossroads.removeAllRoutes();
2222
crossroads.resetState();
@@ -33,8 +33,9 @@ export class Router {
3333
let target: any = evt.currentTarget;
3434
if (target && target.tagName === 'A') {
3535
let href = target.getAttribute('href');
36-
if (href && href.charAt(0) == '/') {
37-
history.push(href);
36+
if (href && href.indexOf(basename + '/') === 0) {
37+
const hrefAfterBasename = href.substring(basename.length);
38+
history.push(hrefAfterBasename);
3839
evt.preventDefault();
3940
}
4041
}
@@ -46,6 +47,10 @@ export class Router {
4647
crossroads.parse((history as any).location.pathname);
4748
}
4849

50+
public link(url: string): string {
51+
return this.history.createHref({ pathname: url });
52+
}
53+
4954
public dispose() {
5055
this.disposeHistory();
5156
$(document).off('click', 'a', this.clickEventListener);

‎templates/KnockoutSpa/Views/Home/Index.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ViewData["Title"] = "Home Page";
33
}
44

5-
<app-root params="history: history"></app-root>
5+
<app-root params="history: history, basename: basename"></app-root>
66

77
@section scripts {
88
<script src="~/dist/main.js" asp-append-version="true"></script>

‎templates/KnockoutSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module.exports = (env) => {
1313
output: {
1414
path: path.join(__dirname, bundleOutputDir),
1515
filename: '[name].js',
16-
publicPath: '/dist/'
16+
publicPath: 'dist/'
1717
},
1818
module: {
1919
rules: [

‎templates/KnockoutSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module.exports = (env) => {
2121
},
2222
output: {
2323
path: path.join(__dirname, 'wwwroot', 'dist'),
24-
publicPath: '/dist/',
24+
publicPath: 'dist/',
2525
filename: '[name].js',
2626
library: '[name]_[hash]',
2727
},

‎templates/ReactReduxSpa/ClientApp/boot-client.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import * as RoutesModule from './routes';
1212
let routes = RoutesModule.routes;
1313

1414
// Create browser history to use in the Redux store
15-
const history = createBrowserHistory();
15+
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
16+
const history = createBrowserHistory({ basename: baseUrl });
1617

1718
// Get the application-wide store instance, prepopulating with state from the server where available.
1819
const initialState = (window as any).initialReduxState as ApplicationState;

‎templates/ReactReduxSpa/ClientApp/boot-server.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ export default createServerRenderer(params => {
1212
return new Promise<RenderResult>((resolve, reject) => {
1313
// Prepare Redux store with in-memory history, and dispatch a navigation event
1414
// corresponding to the incoming URL
15+
const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash
16+
const urlAfterBasename = params.url.substring(basename.length);
1517
const store = configureStore(createMemoryHistory());
16-
store.dispatch(replace(params.location));
18+
store.dispatch(replace(urlAfterBasename));
1719

1820
// Prepare an instance of the application and perform an inital render that will
1921
// cause any async tasks (e.g., data access) to begin
2022
const routerContext: any = {};
2123
const app = (
2224
<Provider store={ store }>
23-
<StaticRouter context={ routerContext } location={ params.location.path } children={ routes } />
25+
<StaticRouter basename={ basename } context={ routerContext } location={ params.location.path } children={ routes } />
2426
</Provider>
2527
);
2628
renderToString(app);

‎templates/ReactReduxSpa/ClientApp/store/WeatherForecasts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const actionCreators = {
4545
requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
4646
// Only load data if it's something we don't already have (and are not already loading)
4747
if (startDateIndex !== getState().weatherForecasts.startDateIndex) {
48-
let fetchTask = fetch(`/api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex }`)
48+
let fetchTask = fetch(`api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex }`)
4949
.then(response => response.json() as Promise<WeatherForecast[]>)
5050
.then(data => {
5151
dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data });

‎templates/ReactReduxSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module.exports = (env) => {
1313
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
1414
output: {
1515
filename: '[name].js',
16-
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
16+
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
1717
},
1818
module: {
1919
rules: [

‎templates/ReactReduxSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = (env) => {
3333
],
3434
},
3535
output: {
36-
publicPath: '/dist/',
36+
publicPath: 'dist/',
3737
filename: '[name].js',
3838
library: '[name]_[hash]',
3939
},

‎templates/ReactSpa/ClientApp/boot.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ let routes = RoutesModule.routes;
1010
function renderApp() {
1111
// This code starts up the React app when it runs in a browser. It sets up the routing
1212
// configuration and injects the app into a DOM element.
13+
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
1314
ReactDOM.render(
1415
<AppContainer>
15-
<BrowserRouter children={ routes } />
16+
<BrowserRouter children={ routes } basename={ baseUrl } />
1617
</AppContainer>,
1718
document.getElementById('react-app')
1819
);

‎templates/ReactSpa/ClientApp/components/FetchData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class FetchData extends React.Component<{}, FetchDataExampleState> {
1111
super();
1212
this.state = { forecasts: [], loading: true };
1313

14-
fetch('/api/SampleData/WeatherForecasts')
14+
fetch('api/SampleData/WeatherForecasts')
1515
.then(response => response.json() as Promise<WeatherForecast[]>)
1616
.then(data => {
1717
this.setState({ forecasts: data, loading: false });

‎templates/ReactSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module.exports = (env) => {
1313
output: {
1414
path: path.join(__dirname, bundleOutputDir),
1515
filename: '[name].js',
16-
publicPath: '/dist/'
16+
publicPath: 'dist/'
1717
},
1818
module: {
1919
rules: [

‎templates/ReactSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module.exports = (env) => {
2121
},
2222
output: {
2323
path: path.join(__dirname, 'wwwroot', 'dist'),
24-
publicPath: '/dist/',
24+
publicPath: 'dist/',
2525
filename: '[name].js',
2626
library: '[name]_[hash]',
2727
},

‎templates/VueSpa/ClientApp/components/fetchdata/fetchdata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default class FetchDataComponent extends Vue {
1313
forecasts: WeatherForecast[] = [];
1414

1515
mounted() {
16-
fetch('/api/SampleData/WeatherForecasts')
16+
fetch('api/SampleData/WeatherForecasts')
1717
.then(response => response.json() as Promise<WeatherForecast[]>)
1818
.then(data => {
1919
this.forecasts = data;

‎templates/VueSpa/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = (env) => {
2323
output: {
2424
path: path.join(__dirname, bundleOutputDir),
2525
filename: '[name].js',
26-
publicPath: '/dist/'
26+
publicPath: 'dist/'
2727
},
2828
plugins: [
2929
new CheckerPlugin(),

‎templates/VueSpa/webpack.config.vendor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports = (env) => {
2828
},
2929
output: {
3030
path: path.join(__dirname, 'wwwroot', 'dist'),
31-
publicPath: '/dist/',
31+
publicPath: 'dist/',
3232
filename: '[name].js',
3333
library: '[name]_[hash]'
3434
},

2 commit comments

Comments
 (2)

enternet commented on Jul 14, 2017

@enternet

That breaks all. All template apps return an exception. Reason: publicPath should starts with '/'

SteveSandersonMS commented on Jul 14, 2017

@SteveSandersonMS
MemberAuthor

@enternet The newer version of Microsoft.AspNetCore.NodeServices handles this OK. It's better for publicPath to be relative (not with a leading slash) because this is what allows your application to work correctly if it's hosted in a virtual directory.

This repository has been archived.