150

I have the AngularJS seed project and I've added

$locationProvider.html5Mode(true).hashPrefix('!');

to the app.js file. I want to configure IIS 7 to route all requests to

http://localhost/app/index.html

so that this works for me. How do I do this?

Update:

I've just discovered, downloaded and installed the IIS URL Rewrite module, hoping this will make it easy and obvious to achieve my goal.

Update 2:

I guess this sums up what I'm trying to achieve (taken from the AngularJS Developer documentation):

Using this mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html)

Update 3:

I'm still working on this and I realise I need to NOT redirect (have rules that rewrite) certain URLs such as

http://localhost/app/lib/angular/angular.js
http://localhost/app/partials/partial1.html

so anything that is in the css, js, lib or partials directories isn't redirected. Everything else will need to be redirected to app/index.html

Anyone know how to achieve this easily without having to add a rule for every single file?

Update 4:

I have 2 inbound rules defined in the IIS URL Rewrite module. The first rule is:

IIS URL Rewrite Inbound Rule 1

The second rule is:

IIS URL Rewrite Inbound Rule 2

Now when I navigate to localhost/app/view1 it loads the page, however the supporting files (the ones in the css, js, lib and partials directories) are also being rewritten to the app/index.html page - so everything is coming back as the index.html page no matter what URL is used. I guess this means my first rule, that is supposed to prevent these URLs from being processed by the second rule, isn't working.. any ideas? ...anyone? ...I feel so alone... :-(

Dean
  • 4,554
  • 7
  • 34
  • 45
  • the second rule is critical: you need to make sure that it's routing to `app/index.html` and _not_ `app/` to explicitly trigger the page that serves up AngularJS. i lost 2 hours of my life before i figured it out. :-) – Dexter Legaspi May 13 '14 at 14:51
  • Another way to use ASP.NET MVC and add to RouteConfig.cs: routes.MapRoute(name: "Default", url: "{*anything}", defaults: new { controller = "Home", action = "Index", } ); and your HomeController Index just returns File("~/index-anyhinghere.html", "text/html"); Then your app becomes IIS independent – Toolkit Apr 20 '15 at 04:09
  • I had to install "URL rewrite module from 'Web Installer'". If url rewrite module not there, then while reloading, rewrite will not work. INSTALL URL rewrite module. :) – Bimal Das Apr 22 '19 at 13:16

9 Answers9

311

I write out a rule in web.config after $locationProvider.html5Mode(true) is set in app.js.

Hope, helps someone out.

  <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>

In my index.html I added this to <head>

<base href="/">

Don't forget to install IIS URL Rewrite on server.

Also if you use Web API and IIS, this will work if your API is at www.yourdomain.com/api because of the third input (third line of condition).

bbodenmiller
  • 3,101
  • 5
  • 34
  • 50
Yagiz Ozturk
  • 5,408
  • 7
  • 29
  • 44
  • 3
    This was very useful and I found it to be the most complete. Thanks! – Mario Oct 20 '14 at 14:55
  • 6
    PERFECT! This one was a tough issue for me, spent hours working on this. Thanks! ADVICE TO ANYONE ELSE: I added index.html to the Then on my index.html (root directory file for Angular SPA, the When I coped the code above, it didn't match my so it did not work correctly. Huge props for the "/(api)" pattern also, I kept stumbling on that part. – Brad Martin Dec 31 '14 at 17:38
  • what if I host on Azure web sites? – Toolkit Apr 20 '15 at 04:11
  • Toolkit, i have the same question. I've basically implemented this exactly as Brad Martin described on Dec 31, but doesn't seem to help. This may be related to Azure. Need to investigate further ... – PeterB May 13 '15 at 06:41
  • I use IIS 7.5 and enable the $locationProvider.html5Mode(true) and the also did the web.config rules but my back button throws me out the app , do you know how to fix it ?? – Cesar Vega Jul 15 '15 at 12:55
  • For bundling and minification to work (assuming all your bundle names contain 'bundles') ; Tested with angular-ui router – DotNetWala Aug 04 '15 at 09:21
  • I tried same thing suggested by @Yagiz Ozturk. Added rules in web.config, added base path in , also installed Url Rewrite 2.0 but still I am getting 404 error. I am using IIS 10 – Microsoft Developer May 31 '16 at 13:35
  • how to change this into web.config RewriteEngine On Options FollowSymLinks RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /#/$1 [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)/(.*)$ /#/$1/$2 [L] – Asad Ali Khan Jun 22 '16 at 04:03
  • @Yagiz Ozturk. After replacing "api" with my my directory name, I am not getting 404 error not the page is loading. After pressing F5, screen becomes blank. I am using components and component routing. After pressing F5, is not loaded at all – Microsoft Developer Jun 23 '16 at 12:05
  • Thanks @YagizOzturk!, This worked after installing the IIS URL Rewrite module 2 – hatsrumandcode Oct 11 '16 at 11:39
  • 10
    It's a joke that we have to jump through these hoops for Angular routing. This kills angular routing for me. What a mess. – user441521 Oct 14 '16 at 19:12
  • The sample above almost worked as writen, I had to correct the path of "(/api) first. Then when I noticed in my client browser > developer mode under Network, my RESTful callbacks were not showing up, I added a forth rule {REQUEST_URI} pattern="(/*.scriptName)" negate="true" everything started working. Note: if I hit refresh button, Angular does not know how to reset state on the route. This is a developer (me) bug or feature depending on how one looks at the world. Thanks for your post Ozturk! – Harvey Mushman Nov 12 '16 at 19:18
  • This worked perfect. Except when trying to invoke the OAuthAuth with Asp.Net WebApi. My `TokenEndpointPath` was the default and not under `/api`. Consider adding `` to the end of the list of conditions. Then your authentication will work as well. – Tjaart van der Walt Dec 06 '16 at 18:36
  • Works for me, thanks! But I had the URL Rewrite module already installed. When I added the rewrite rule it wasn't working. I needed to go to "Programs and Features", find the "IIS URL Rewrite Module 2" and then call repair for this module. Now it's working! – goflo Apr 20 '17 at 07:31
  • 1
    PERFECT! shame that the angular team took your code but did not mention the `api` part – bresleveloper Jun 30 '19 at 22:11
  • This solution also works Vue.js application when you have a baubled files and start from the root to open index.html. Works on Azure App Service with web app in the root (wwwroot) folder. Documentation shows same config - [Docs](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) – Digiman May 18 '20 at 11:38
  • I can't explain how much time researching on this I've been wasting, this was the last configuration I needed to make so my MF page can work in different servers. And this is just to test it in localhost, I'm wondering how many other hippie configurations I will need to make once it's in the cloud. – Yogurtu Jan 29 '23 at 17:39
40

The IIS inbound rules as shown in the question DO work. I had to clear the browser cache and add the following line in the top of my <head> section of the index.html page:

<base href="/myApplication/app/" />

This is because I have more than one application in localhost and so requests to other partials were being taken to localhost/app/view1 instead of localhost/myApplication/app/view1

Hopefully this helps someone!

Dean
  • 4,554
  • 7
  • 34
  • 45
  • 1
    I didn't need this, but the question edits were great. Thanks! – Ash Clarke Jan 24 '13 at 23:05
  • 2
    Also this can be useful. Worked for me when reloading page. Without any special rules for every file: https://coderwall.com/p/mycbiq – Per G Mar 27 '14 at 18:17
  • as @ash-clarke said, it's not necessary but it's good practice anyway as it allows the links on the page to be agnostic about which "subdirectory" it belongs to. – Dexter Legaspi May 13 '14 at 14:54
  • @PerG I ran into the above solution working but not on reload, here is my code: http://stackoverflow.com/questions/25644287/iis-url-rewrite-rules Any ideas? – amcdnl Sep 03 '14 at 12:26
  • I do not know or have tried ti filter out certain calls. A way around would be to have API on other domain etc. But perhaps this is not a solution for u. But u can continue to ask in my link above. And maybe the author can answer you? – Per G Sep 04 '14 at 12:44
  • Thanks for this and for everyone's comments. In my case, since I'm using a .NET MVC structure, instead of specifying a page for the rewrite rule, I specified the URL to the MVC route to the index page and that did it. (ie. http://localhost/Home/Index). Also, I had to account for the api calls in the rewrite rule. In any case, this thread pointed me to the right direction! Thanks – Naner Mar 19 '15 at 13:58
  • You can also do something like this: [HttpGet, Route("", Order = 1), Route("{angularRoute}", Order = 2)] public ViewResult Index() { return View(); } This may be what @Nanar is referring to. – Markus May 21 '16 at 21:19
  • Had the same problem; clearing the cache apparently is key as well, didn't get it to work otherwise. – rumblefx0 Oct 10 '16 at 13:22
  • Just to add this solved my problem. Had all the rewrite rules in and refresh on a single level url was fine i.e. www.mywebsite/list, however refresh on www.mywebsite/heros/list caused an error. Changing my href from app/ to /app/ fixed it. – andyfinch Dec 04 '19 at 10:29
13

The issue with only having these two conditions:

  <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
  <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />

is that they work only as long as the {REQUEST_FILENAME} exists physically on disk. This means that there can be scenarios where a request for an incorrectly named partial view would return the root page instead of a 404 which would cause angular to be loaded twice (and in certain scenarios it can cause a nasty infinite loop).

Thus, some safe "fallback" rules would be recommended to avoid these hard to troubleshoot issues:

  <add input="{REQUEST_FILENAME}" pattern="(.*?)\.html$" negate="true" />
  <add input="{REQUEST_FILENAME}" pattern="(.*?)\.js$" negate="true" />
  <add input="{REQUEST_FILENAME}" pattern="(.*?)\.css$" negate="true" />

or a condition that matches any file ending:

<conditions>
  <!-- ... -->
  <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" />
</conditions>
Ciprian Teiosanu
  • 1,553
  • 16
  • 19
  • 1
    With all of this in place my site loads just fine, but if I am at a path along the route I have issues. Thus, if the path in my browser is: http://10.34.34.46/jbvdev/documents/BC498484 Initially it shows up fine, but if I hit refresh, everything breaks because it begins looking for the css and js at http://10.34.34.46/jbvdev/documents/css or http://10.34.34.46/jbvdev/documents/js Does anyone know how to solve this one? – Michael Mahony Jan 05 '16 at 18:35
12

In my case I kept getting a 403.14 after I had setup the correct rewrite rules. It turns out that I had a directory that was the same name as one of my URL routes. Once I removed the IsDirectory rewrite rule my routes worked correctly. Is there a case where removing the directory negation may cause problems? I can't think of any in my case. The only case I can think of is if you can browse a directory with your app.

<rule name="fixhtml5mode" stopProcessing="true">
  <match url=".*"/>
  <conditions logicalGrouping="MatchAll">
    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
  </conditions>
  <action type="Rewrite" url="/" />
</rule>
Josh C
  • 7,461
  • 3
  • 24
  • 21
4

The top solution for this unfortunately did not work for me using .NET framework 4.5 and IIS 6.2. I got it to work by first following all the steps to install the URL Rewrite tool from Microsoft, then adjusting my Web.config file to include the following:

    <system.webServer>
    <!-- handlers etc -->
    <rewrite>
      <rules>
        <rule name="Angular Routes" stopProcessing="true">
          <match url="(^(?!.*\.[\d\w]+$).*)" />
          <conditions logicalGrouping="MatchAny">
            <add input="{REQUEST_URI}" pattern=".*\/api\/.*" negate="true" />
          </conditions>
          <action type="Rewrite" url="./index.html" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>

The first regular expression uses negative look-aheads to NOT match any file extension at the end of the URI. Then, inside the condition, any URI including /api/ is also ignored.

This configuration allows me to refresh the Angular app without breaking the routing and also access my API without being re-written.

bennfuller
  • 41
  • 1
3

I had a similar issue with Angular and IIS throwing a 404 status code on manual refresh and tried the most voted solution but that did not work for me. Also tried a bunch of other solutions having to deal with WebDAV and changing handlers and none worked.

Luckily I found this solution and it worked (took out parts I didn't need). So if none of the above works for you or even before trying them, try this and see if that fixes your angular deployment on iis issue.

Add the snippet to your webconfig in the root directory of your site. From my understanding, it removes the 404 status code from any inheritance (applicationhost.config, machine.config), then creates a 404 status code at the site level and redirects back to the home page as a custom 404 page.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/index.html" responseMode="ExecuteURL"/>
    </httpErrors>
  </system.webServer>
</configuration>
Melvin Gaye
  • 141
  • 2
  • 3
1

The easiest way I found is just to redirect the requests that trigger 404 to the client. This is done by adding an hashtag even when $locationProvider.html5Mode(true) is set.

This trick works for environments with more Web Application on the same Web Site and requiring URL integrity constraints (E.G. external authentication). Here is step by step how to do

index.html

Set the <base> element properly

<base href="@(Request.ApplicationPath + "/")">

web.config

First redirect 404 to a custom page, for example "Home/Error"

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Home/Error" />
    </customErrors>
</system.web>

Home controller

Implement a simple ActionResult to "translate" input in a clientside route.

public ActionResult Error(string aspxerrorpath) {
    return this.Redirect("~/#/" + aspxerrorpath);
}

This is the simplest way.


It is possible (advisable?) to enhance the Error function with some improved logic to redirect 404 to client only when url is valid and let the 404 trigger normally when nothing will be found on client. Let's say you have these angular routes

.when("/", {
    templateUrl: "Base/Home",
    controller: "controllerHome"
})
.when("/New", {
    templateUrl: "Base/New",
    controller: "controllerNew"
})
.when("/Show/:title", {
    templateUrl: "Base/Show",
    controller: "controllerShow"
})

It makes sense to redirect URL to client only when it start with "/New" or "/Show/"

public ActionResult Error(string aspxerrorpath) {
    // get clientside route path
    string clientPath = aspxerrorpath.Substring(Request.ApplicationPath.Length);

    // create a set of valid clientside path
    string[] validPaths = { "/New", "/Show/" };

    // check if clientPath is valid and redirect properly
    foreach (string validPath in validPaths) {
        if (clientPath.StartsWith(validPath)) {
            return this.Redirect("~/#/" + clientPath);
        }
    }

    return new HttpNotFoundResult();
}

This is just an example of improved logic, of course every web application has different needs

Naigel
  • 9,086
  • 16
  • 65
  • 106
0

I've been trying to deploy a simple Angular 7 application, to an Azure Web App. Everything worked fine, until the point where you refreshed the page. Doing so, was presenting me with an 500 error - moved content. I've read both on the Angular docs and in around a good few forums, that I need to add a web.config file to my deployed solution and make sure the rewrite rule fallback to the index.html file. After hours of frustration and trial and error tests, I've found the error was quite simple: adding a tag around my file markup.

0

I realize this may be an old question, but here is another solution without using a web.config file.

I have 2 separate projects: FrontEnd (Angular) and BackEnd (Web API .NET Core).

  1. Modify angular.json outputPath under build => options: "../BackEnd/wwwroot"

  2. In Program.cs: app.UseDefaultFiles(); app.UseStaticFiles();

    Instructs the server to look for index.html by default in the wwwroot directory.

  3. ng build -- which created a wwwroot folder and created all the static angular files in that folder.

  4. Instead of the rewrite rules in web.config, I created a new Controller as follows:

FallbackController.cs

[Route("api/[controller]")]
[ApiController]
public class FallbackController : ControllerBase
{
   return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/HTML");
}
  1. In Program.cs: app.MapFallbackToController("Index", "Fallback");

This tells our API that routing responsibility belongs to Angular and can be found in the Fallback controller, which in turn, points to index.html for instructions on Routing.

Program.cs

// prior code
app.UseAuthentication();
app.UseAuthorization();

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapControllers();

app.MapFallbackToController("Index", "Fallback");

app.Run();
Pavel
  • 704
  • 11
  • 25