314
votes

I have a project that requires my URLs have dots in the path. For example I may have a URL such as www.example.com/people/michael.phelps

URLs with the dot generate a 404. My routing is fine. If I pass in michaelphelps, without the dot, then everything works. If I add the dot I get a 404 error. The sample site is running on Windows 7 with IIS8 Express. URLScan is not running.

I tried adding the following to my web.config:

<security>
  <requestFiltering allowDoubleEscaping="true"/>
</security>

Unfortunately that didn't make a difference. I just receive a 404.0 Not Found error.

This is a MVC4 project but I don't think that's relevant. My routing works fine and the parameters I expect are there, until they include a dot.

What do I need to configure so I can have dots in my URL?

18
Can't believe I spent so much time on this one. The URL works fine if I add a trailing slash. For example, www.example.com/people/michael.phelps/ however without the trailing slash IIS throws a 404 error.Mark
Mark - that is because without the trailing slash, IIS thinks it is a file that it should go and find. Adding the slash has the effect of...this is not a real file. Additionally, the configuration option below tells IIS that if it is not a file, try to route it instead.Tommy
I'm having the same problem after I updated my project to mvc 4 + asp.net 4.5.Tadeu Maia
As a work around I'm using IIS Rewrite to add the trailing slash to my URLs.Mark
This doesn't work for me. The URL works fine with "." within the URL but when it is at the very end it gives an errorArcadian

18 Answers

386
votes

I got this working by editing my site's HTTP handlers. For my needs this works well and resolves my issue.

I simply added a new HTTP handler that looks for specific path criteria. If the request matches it is correctly sent to .NET for processing. I'm much happier with this solution that the URLRewrite hack or enabling RAMMFAR.

For example to have .NET process the URL www.example.com/people/michael.phelps add the following line to your site's web.config within the system.webServer / handlers element:

<add name="ApiURIs-ISAPI-Integrated-4.0"
     path="/people/*"
     verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
     type="System.Web.Handlers.TransferRequestHandler"
     preCondition="integratedMode,runtimeVersionv4.0" />

Edit

There are other posts suggesting that the solution to this issue is RAMMFAR or RunAllManagedModulesForAllRequests. Enabling this option will enable all managed modules for all requests. That means static files such as images, PDFs and everything else will be processed by .NET when they don't need to be. This options is best left off unless you have a specific case for it.

49
votes

After some poking around I found that relaxedUrlToFileSystemMapping did not work at all for me, what worked in my case was setting RAMMFAR to true, the same is valid for (.net 4.0 + mvc3) and (.net 4.5 + mvc4).

<system.webserver>
    <modules runAllManagedModulesForAllRequests="true">

Be aware when setting RAMMFAR true Hanselman post about RAMMFAR and performance

29
votes

I believe you have to set the property relaxedUrlToFileSystemMapping in your web.config. Haack wrote an article about this a little while ago (and there are some other SO posts asking the same types of question)

<system.web>
<httpRuntime relaxedUrlToFileSystemMapping="true" />

Edit From the comments below, later versions of .NET / IIS may require this to be in the system.WebServer element.

<system.webServer>
<httpRuntime relaxedUrlToFileSystemMapping="true" />
26
votes

I got stuck on this issue for a long time following all the different remedies without avail.

I noticed that when adding a forward slash [/] to the end of the URL containing the dots [.], it did not throw a 404 error and it actually worked.

I finally solved the issue using a URL rewriter like IIS URL Rewrite to watch for a particular pattern and append the training slash.

My URL looks like this: /Contact/~firstname.lastname so my pattern is simply: /Contact/~(.*[^/])$

I got this idea from Scott Forsyth, see link below: http://weblogs.asp.net/owscott/handing-mvc-paths-with-dots-in-the-path

21
votes

Just add this section to Web.config, and all requests to the route/{*pathInfo} will be handled by the specified handler, even when there are dots in pathInfo. (taken from ServiceStack MVC Host Web.config example and this answer https://stackoverflow.com/a/12151501/801189)

This should work for both IIS 6 & 7. You could assign specific handlers to different paths after the 'route' by modifying path="*" in 'add' elements

  <location path="route">
    <system.web>
      <httpHandlers>
        <add path="*" type="System.Web.Handlers.TransferRequestHandler" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" />
      </httpHandlers>
    </system.web>
    <!-- Required for IIS 7.0 -->
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true" />
      <validation validateIntegratedModeConfiguration="false" />
      <handlers>
        <add name="ApiURIs-ISAPI-Integrated-4.0" path="*" type="System.Web.Handlers.TransferRequestHandler" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" preCondition="integratedMode,runtimeVersionv4.0" />
      </handlers>
    </system.webServer>
  </location>
6
votes

MVC 5.0 Workaround.

Many of the suggested answers doesn't seem to work in MVC 5.0.

As the 404 dot problem in the last section can be solved by closing that section with a trailing slash, here's the little trick I use, clean and simple.

While keeping a convenient placeholder in your view:

@Html.ActionLink("Change your Town", "Manage", "GeoData", new { id = User.Identity.Name }, null)

add a little jquery/javascript to get the job done:

<script>
    $('a:contains("Change your Town")').on("click", function (event) {
        event.preventDefault();
        window.location.href = '@Url.Action("Manage", "GeoData", new { id = User.Identity.Name })' + "/";
    });</script>

please note the trailing slash, that is responsible for changing

http://localhost:51003/GeoData/Manage/[email protected]

into

http://localhost:51003/GeoData/Manage/[email protected]/
5
votes

Super easy answer for those that only have this on one webpage. Edit your actionlink and a + "/" on the end of it.

  @Html.ActionLink("Edit", "Edit", new { id = item.name + "/" }) |
3
votes

Depending on how important it is for you to keep your URI without querystrings, you can also just pass the value with dots as part of the querystring, not the URI.

E.g. www.example.com/people?name=michael.phelps will work, without having to change any settings or anything.

You lose the elegance of having a clean URI, but this solution does not require changing or adding any settings or handlers.

2
votes

You might want to think about using dashes instead of periods.

In Pro ASP MVC 3 Framework they suggest this about making friendly URLs:

Avoid symbols, codes, and character sequences. If you want a word separator, use a dash (/my-great-article). Underscores are unfriendly, and URL-encoded spaces are bizarre (/my+great+article) or disgusting (/my%20great%20article).

It also mentions that URLs should be be easy to read and change for humans. Maybe a reason to think about using a dash instead of a dot also comes from the same book:

Don't use file name extensions for HTML pages (.aspx or .mvc), but do use them for specialized file types (.jpg, .pdf, .zip, etc). Web browsers don't care about file name extensions if you set the MIME type appropriately, but humans still expect PDF files to end with .pdf

So while a period is still readable to humans (though less readable than dashes, IMO), it might still be a bit confusing/misleading depending on what comes after the period. What if someone has a last name of zip? Then the URL will be /John.zip instead of /John-zip, something that can be misleading even to the developer that wrote the application.

1
votes

Would it be possible to change your URL structure?
For what I was working on I tried a route for

url: "Download/{fileName}"

but it failed with anything that had a . in it.

I switched the route to

    routes.MapRoute(
        name: "Download",
        url:  "{fileName}/Download",
        defaults: new { controller = "Home", action = "Download", }
    );

Now I can put in localhost:xxxxx/File1.doc/Download and it works fine.

My helpers in the view also picked up on it

     @Html.ActionLink("click here", "Download", new { fileName = "File1.doc"})

that makes a link to the localhost:xxxxx/File1.doc/Download format as well.

Maybe you could put an unneeded word like "/view" or action on the end of your route so your property can end with a trailing / something like /mike.smith/view

1
votes

As solution could be also considering encoding to a format which doesn't contain symbol., as base64.

In js should be added

btoa(parameter); 

In controller

byte[] bytes = Convert.FromBase64String(parameter);
string parameter= Encoding.UTF8.GetString(bytes);
1
votes

It's as simple as changing path="." to path="". Just remove the dot in the path for ExensionlessUrlHandler-Integrated-4.0 in web.config.

Here's a nice article https://weblog.west-wind.com/posts/2015/Nov/13/Serving-URLs-with-File-Extensions-in-an-ASPNET-MVC-Application

1
votes

Tried all the solutions above but none of them worked for me. What did work was I uninstalling .NET versions > 4.5, including all its multilingual versions; Eventually I added newer (English only) versions piece by piece. Right now versions installed on my system is this:

  • 2.0
  • 3.0
  • 3.5 4
  • 4.5
  • 4.5.1
  • 4.5.2
  • 4.6
  • 4.6.1

And its still working at this point. I'm afraid to install 4.6.2 because it might mess everything up.

So I could only speculate that either 4.6.2 or all those non-English versions were messing up my configuration.

0
votes

I was able to solve my particular version of this problem (had to make /customer.html route to /customer, trailing slashes not allowed) using the solution at https://stackoverflow.com/a/13082446/1454265, and substituting path="*.html".

0
votes

Add URL Rewrite rule to Web.config archive. You need to have the URL Rewrite module already installed in IIS. Use the following rewrite rule as inspiration for your own.

<?xml version="1.0" encoding="utf-8"?>
<configuration>

<system.webServer>
  <rewrite>
    <rules>
      <rule name="Add trailing slash for some URLs" stopProcessing="true">
        <match url="^(.*(\.).+[^\/])$" />
          <conditions>
              <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
              <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Redirect" url="{R:1}/" />
      </rule>
    </rules>
    </rewrite>
</system.webServer>

</configuration> 
0
votes

Also, (related) check the order of your handler mappings. We had a .ashx with a .svc (e.g. /foo.asmx/bar.svc/path) in the path after it. The .svc mapping was first so 404 for the .svc path which matched before the .asmx. Havn't thought too much but maybe url encodeing the path would take care of this.

0
votes

This is the best solution I have found for the error 404 on IIS 7.5 and .NET Framework 4.5 environment, and without using: runAllManagedModulesForAllRequests="true".

I followed this thread: https://forums.asp.net/t/2070064.aspx?Web+API+2+URL+routing+404+error+on+IIS+7+5+IIS+Express+works+fine and I have modified my web.config accordingly, and now the MVC web app works well on IIS 7.5 and .NET Framework 4.5 environment.

-3
votes
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebApplication1.Controllers
{
    [RoutePrefix("File")]
    [Route("{action=index}")]
    public class FileController : Controller
    {
        // GET: File
        public ActionResult Index()
        {
            return View();
        }

        [AllowAnonymous]
        [Route("Image/{extension?}/{filename}")]
        public ActionResult Image(string extension, string filename)
        {
            var dir = Server.MapPath("/app_data/images");

            var path = Path.Combine(dir, filename+"."+ (extension!=null?    extension:"jpg"));
           // var extension = filename.Substring(0,filename.LastIndexOf("."));

            return base.File(path, "image/jpeg");
        }
    }
}