skip to Main Content

I am building an application that is an ASP.NET MVC and Angular 1.x hybrid. I use MVC as a shell and then layered Angular on top of it. I did this for a couple of reasons. First, it provided me with the ability to create an HTML helper class for script references to support cache busting. Second, it allows me to “push” a set of server side configurations to angular for things like web service URLs, etc. All of this seems to be working ok except for a single issue.

Currently, I am having to process a couple of IIS rewrite rules to make all of this work properly. In testing in a staged environment today, I published a link to Facebook that does not appear to work properly though and I’m not really sure why. Based on my understanding of the rule syntax, I thought it would work, but isn’t.

Here are the IIS rewrite rules I currently have in web.config and why I have each rule:

  <system.webServer>
    <rewrite xdt:Transform="Replace">
      <rules>
        <rule name="cachebust">
          <match url="([S]+)(/v-[0-9]+/)([S]+)" />
          <action type="Rewrite" url="{R:1}/{R:3}" />
        </rule>
        <rule name="baseindex" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
        <rule name="non-www" stopProcessing="true">
          <match url="(.*)" negate="false"></match>
          <conditions>
            <add input="{HTTP_HOST}" pattern="^exampledomain.org$" negate="true"></add>
          </conditions>
          <action type="Redirect" url="http://exampledomain.org/{R:1}"></action>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>

1) The “cachebust” rule is used for script cache busting. The HTML helper I wrote takes the path of the script file and inserts a fake version # into the path. For example, “scripts/example.js” would become “scripts/v-123456789/example.js”, where “123456789” are the ticks from the current date and time. This rule will rewrite the URL back to the original but since the page is output with the version stamped path, it will force my angular scripts to be reloaded by the browser in the event that they change. This rule appears to be working fine. Credit to Mads for this: https://madskristensen.net/blog/cache-busting-in-aspnet/

2) The “baseindex” rule is used to rewrite the URL so that ASP.NET will always serve the index.cshtml page that is used as the root for my site. This rule also appears to work fine. Credit to this SO QA: How do I configure IIS for URL Rewriting an AngularJS application in HTML5 mode?

3) The “non-www” rule is the problematic one. The goal of the rule is to redirect all www requests to non-www requests based on ScottGu’s blog post (link below) about canonical host names and how they affect SEO. This rule works perfectly fine for www.example.org as it redirects the URL to example.org, but it fails when the URL looks like this: www.example.org/entity/1 where entity is a detail page for id = 1. In practice, it fails for www.example.org/about too.
Since my web services require CORS to be enabled as they are on separate domains, this also needs to happen for that.
https://weblogs.asp.net/scottgu/tip-trick-fix-common-seo-problems-using-the-url-rewrite-extension

For sake of completeness: HTML 5 mode is enabled for Angular and my route to the specified page is defined as such:

.state('entityDetail', {
                    url: '/entity/{id}',
                    templateUrl: 'app/pages/entity/entityDetail.html',
                    controller: 'entityDetailController',
                    controllerAs: 'entityDetailCtrl'
                })

Also, my _Layout.cshtml contains the base href of:

<base href="/" />

and script references like such (except for the angular scripts that require the cachebust rule listed above):

<script type="text/javascript" src="scripts/jquery-2.2.0.min.js"></script>

I’ve tried several different versions of the rule, but they all lead to different issues. Since there are so many different things at play here, I am concerned that I am overlooking something or that I am wrong in my thinking on how this “should” work.

Any help would be greatly appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    It took some trial and error to figure this all out, but wanted to post the answer I finally came up with in the event that someone else ever tries to marry ASP.NET MVC and Angular 1.x together while allowing cache busting in Google Chrome.

    It turned out to be a couple of things:

    1) The order of the rules.

    2) The stopProcessing flag and when it should be enabled.

    In summary, to get this to work, I built the rules so that all requests must be redirected to www.exampledomain.com and then stop processing rules until the reprocessed request happens (this time www. will be guaranteed). Then, the cachebust rule must happen BEFORE the rewrite to the base index of "/", otherwise, the entire HTML document is returned by the server when each of the script files that must be "busted" load. This was really the majority of my issue from the very beginning. The whole process was honestly a little confusing so if this answer is not sufficient, please comment and I will try my best to better articulate the "whys" I found in my research. Hope this helps someone else though...it only took 10 days to figure out. :)

    <system.webServer>
        <rewrite xdt:Transform="Replace">
          <rules>
            <rule name="forcewww" patternSyntax="ECMAScript" stopProcessing="true">
              <match url=".*" />
              <conditions logicalGrouping="MatchAny">
                <add input="{HTTP_HOST}" pattern="^exampledomain.com$" />
              </conditions>
              <action type="Redirect" url="http://www.exampledomain.com/{R:0}" />
            </rule>
            <rule name="cachebust">
              <match url="([S]+)(/v-[0-9]+/)([S]+)" />
              <action type="Rewrite" url="{R:1}/{R:3}" />
            </rule>
            <rule name="baseindex">
              <match url=".*" />
              <conditions logicalGrouping="MatchAll">
                <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
              </conditions>
              <action type="Rewrite" url="/" />
            </rule>
          </rules>
        </rewrite>
      </system.webServer>
    

  2. Instead of writing redirect rules in an app.config file, you can use the following in your Program.cs file:

    var builder = WebApplication.CreateBuilder(args); // Followed by service configurations...
    var app = builder.Build(); // followed by app configurations...
    
    // The following will fix your redirect woes:
    app.UseStaticFiles();
    app.MapFallbackToFile("index.html");
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search