Integrate Webpack and ASP.NET MVC/WebAPI

Quick tour of some tricks I’m using in my Podcasts-Angular2 demo project.

While that project is using Angular2, everything discussed here applies to React or Angular 1.x or any other FE framework you might be using, as long as it builds with webpack.

In fact, not too much here is even webpack specific. It could work easily with any FE framework or tooling that provides some sort of “serve” command that compiles and hosts assets during development time, such as ember serve or the http-server npm package.

Note: this approach makes sense for projects where you want to mix server side razor rendering and javascript rendering. For example, perhaps some of the shell of your site is rendered serverside, but the SPA framework only controls a big ol’ div in the center of the page. If the entire app is driven from the client side, you can use webpack-dev-server’s proxy feature to proxy to the API endpoints

Using webpack-dev-server for development

In development, I want to link the FE assets to come from webpack-dev-server. This allows for FE auto-reload, or hot module replacement, recompilation, etc.

In my layout Razor view, I use a configuration setting to switch between local assets and assets served from webpack-dev-server.

I like to use a settings class to wrap ConfigurationManager.

Settings.cs
1
2
3
4
5
6
public static class Settings
{
public static bool UseWebpackDevServer => "true".Equals(ConfigurationManager.AppSettings["UseWebpackDevServer"], StringComparison.InvariantCultureIgnoreCase);

public static string WebpackDevServerRoot => ConfigurationManager.AppSettings["WebpackDevServerRoot"];
}

The layout looks something like this. When UseWebpackDevServer is true, assets like CSS and javascript are linked to the webpack-dev-server URL. Now when I save a FE file, it picks up the change, recompiles the bundles, and automatically reloads the page. React projects can similarly benefit from hot-module reloading.

_Layout.cshtml
1
2
3
4
5
6
7
8
9
@if (Settings.UseWebpackDevServer)
{
<link href="@(Settings.WebpackDevServerRoot)/vendor.css" rel="stylesheet">
<link href="@(Settings.WebpackDevServerRoot)/app.css" rel="stylesheet">
}
else
{
<!-- local assets, discussed below -->
}

Then the web.config looks something like this. The UseWebpackDevServer key can be set to false in production, or omitted entirely. The WebpackDevServerRoot key is not needed in production either.

Web.config
1
2
3
4
<appSettings>
<add key="UseWebpackDevServer" value="true"/>
<add key="WebpackDevServerRoot" value="http://localhost:8080"/>
</appSettings>

Now I can run my project from visual studio to provide the REST API and to serve the application shell markup.

Using local assets for production

In production, I don’t want to use webpack-dev-server, I want the compiled and minified assets on disk so they can be served by IIS.

I just omit the UseWebpackDevServer configuration value from my web.config in production (or set it to "false").

_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
 @if (Settings.UseWebpackDevServer)
{
<script type="text/javascript" src="@(Settings.WebpackDevServerRoot)/polyfills.js"></script>
<script type="text/javascript" src="@(Settings.WebpackDevServerRoot)/vendor.js"></script>
<script type="text/javascript" src="@(Settings.WebpackDevServerRoot)/app.js"></script>
}
else
{
<script type="text/javascript" src="/static/polyfills.min.js"></script>
<script type="text/javascript" src="/static/vendor.min.js"></script>
<script type="text/javascript" src="/static/app.min.js"></script>
}

Now that Settings.UseWebpackDevServer is false, the script tags all point to the /static/ folder instead.

But how to get the compiled files there?

My package.json for the FE build defines a command to copy the compiled assets to a folder in the web root of the project.

package.json
1
2
3
4
"scripts": {
"start": "webpack-dev-server --inline --progress --port 8080",
"build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail && ncp dist ../Podcasts.Web/static"
}

Scroll right

I’m using the ncp package to copy files from webpack’s dist output folder over to the static folder within the server side project. I could also have had webpack output right to that folder.

Since these are output files compiled by the webpack build process, I don’t want them stored in source control. Thus I add the dist folder and the static folder to .gitignore.

.gitignore
1
2
dist/
Podcasts.Web/static/

Deployment

msdeploy doesn’t send files not included in the project file.

To get the assets to deploy, I needed to add the files to the .csproj in Visual Studio (Right click -> Include in Project) or modify the project file to automatically include them.

Integrating with client side routing

Since I’m using client side routing in HTML5 mode (no hash based URLs) I need to have IIS just serve the shell HTML for any request. That way if users bookmark a URL or send a link to a friend, the server will not 404, but send the shell markup and let the client side router set up the page and load data from the APIs.

I just use a simple HomeController that renders a Razor view for the shell, and the URL Rewrite module configured in web.config to use it:

web.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<system.webServer>
<rewrite>
<rules>
<rule name="AngularJS Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>

Thanks to Yagiz Ozturk on StackOverflow for that bit of magic.

This tells IIS to match all routes, except any URL that is a static file on disk, or is a directory, or starts with /api and instead pretend it was really a request for just /. This hits the HomeController.Index action and renders the application shell markup. Then the client side router spins up, reads the URL from the address bar and performs the correct actions.

As long as the APIs live under /api everything works just fine.

Conclusion

I’ve used this pattern successfully for a few side projects, both React and angular 2.

I hope someone finds it useful.