I quite like the approach of the new asp.net (asp.net 5\\core 1.0) web apps with the wwwroot folder being the root and only static files in there being served up.
I believe I have a working method for doing this now. Took a bit of googling and experimentation, but in the end I came up with the following process:
Create a new ASP.NET 4.5 project in VS2015, selecting the Empty Template
Add OWIN references through nuget (Install-Package Microsoft.Owin.Host.SystemWeb and Microsoft.Owin.StaticFiles)
Add a startup file similar to this:
[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp.UI
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
string root = AppDomain.CurrentDomain.BaseDirectory;
var physicalFileSystem = new PhysicalFileSystem(Path.Combine(root, "wwwroot"));
var options = new FileServerOptions
{
RequestPath = PathString.Empty,
EnableDefaultFiles = true,
FileSystem = physicalFileSystem
};
options.StaticFileOptions.FileSystem = physicalFileSystem;
options.StaticFileOptions.ServeUnknownFileTypes = false;
app.UseFileServer(options);
}
}
}
Add the following to your web.config file, to prevent IIS from serving static files you don't want it to, and force everything through the OWIN pipeline:
<system.webServer>
<handlers>
<remove name="StaticFile"/>
<add name="Owin" verb="" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb"/>
</handlers>
</system.webServer>
I'm always open to better suggestions on how to do this, but this at least seems to work for me.
Although OWIN worked for me in my dev environment running in VS 2017, it didn't work once deployed to azure. We run a SPA as well, and are storing the webpack output in ~/wwwroot/ but wanted it to load as if it was in the project root ~/ just like .net core webapi does it. I accomplished it by using just the URL rewrite shown below:
<system.webServer>
<rewrite>
<rules>
<rule name="wwwRootFix" 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="{APPL_PHYSICAL_PATH}wwwroot\{R:1}" matchType="IsFile" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(wwwroot)" negate="true" />
</conditions>
<action type="Redirect" url="/wwwroot/{R:1}" />
</rule>
<rule name="React Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(wwwroot)" negate="true" />
</conditions>
<action type="Rewrite" url="/wwwroot/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
The first rule ensures that the file you are looking for doesn't exist in the root, isn't a folder, is found in the new path, isn't the wwwroot folder, nor is it part of the api. If ALL of those conditions are met, it tries to load the data from wwwroot.
The second rule checks to make sure you aren't trying to load the api, or a file that actually exists at the root. If both conditions are met, it loads the default SPA html document (in our case we use react).
Effectively this allows react-router 4 to handle all other routes after all the other conditions above have failed to find a match, and make it so it doesn't throw a 404 error when trying to load one of the react-router routes.
If you would like to use IIS for this you can create a rewrite rule:
<system.webServer>
<rewrite>
<rules>
<rule name="RewriteUnknownToIndex" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^wwwroot/.*" negate="true" />
<action type="Rewrite" url="/wwwroot/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
You can also add a prefix to the url regex, such as ^(wwwroot|api)/.*, if this is a web-api that you would like to host at /api.