119

The URL I'm trying to let work is one in the style of: http://somedomain.com/api/people/staff.33311 (just like sites as LAST.FM allow all sort of signs in their RESTFul & WebPage urls, for example "http://www.last.fm/artist/psy'aviah" is a valid url for LAST.FM).

What works are following scenarios: - http://somedomain.com/api/people/ - which returns all people - http://somedomain.com/api/people/staff33311 - would work as well, but it's not what I'm after I'd want the url to accept a "dot", like the example below - http://somedomain.com/api/people/staff.33311 - but this gives me a

HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

I've set up following things:

  1. The controller "PeopleController"

    public IEnumerable<Person> GetAllPeople()
    {
        return _people;
    }
    
    public IHttpActionResult GetPerson(string id)
    {
        var person = _people.FirstOrDefault(p => p.Id.ToLower().Equals(id.ToLower()));
        if (person == null)
            return NotFound();
    
        return Ok(person);
    }    
    
  2. The WebApiConfig.cs

    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
    
        // Web API routes
        config.MapHttpAttributeRoutes();
    
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
    

I already tried following all the tips of this blogpost http://www.hanselman.com/blog/ExperimentsInWackinessAllowingPercentsAnglebracketsAndOtherNaughtyThingsInTheASPNETIISRequestURL.aspx but it still won't work.. I also think it's quite tedious and I wonder if there isn't another, better and more secure way.

We have our Id's internally like this, so we're going to have to find a solution to fit the dot in one way or another, preferably in the style of "." but I'm open to alternative suggestions for urls if need be...

Yves Schelpe
  • 3,343
  • 4
  • 36
  • 69
  • 11
    Not an answer, but as to *why* your're getting a 404 for http://somedomain.com/api/people/staff.33311 - by default, IIS looks at this URL and sees the . as a file extension and invokes the static file handler, bypassing your MVC API. The answer you accepted (running all managed modules for all requests) works because you force every request to IIS to go through the ASP.NET pipeline (therefore, your controllers) – Henry C Jan 15 '15 at 16:15
  • Closely related post [here](https://stackoverflow.com/q/2087246/465053). – RBT Oct 19 '17 at 02:19

9 Answers9

151

Suffix the URL with a slash e.g. http://somedomain.com/api/people/staff.33311/ instead of http://somedomain.com/api/people/staff.33311.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111
  • 3
    @AgustinMeriles my answer could be considered more of a workaround than an actual answer, depends on your interpretation of the question. – Danny Varod Oct 17 '15 at 13:12
  • 5
    That does not work though when the dot is at the end of the URL section as in /people/member. – eYe Oct 23 '15 at 13:18
  • If the input isn't simple, perhaps an odata style query suffix is more suitable that a rest URL. – Danny Varod Oct 23 '15 at 19:10
  • 2
    No, what if I don't want a slash at the end?! It should _not_ be required. – Josh M. Jan 06 '16 at 18:15
  • 1
    @JoshM. This is a workaround, I didn't write ASP.NET. Also, the slash doesn't affect the request. It does, however, help make your intentions more clear as in `google.com/my query goes here/` vs. `google.com/subDomain my query goes here`. – Danny Varod Jan 06 '16 at 18:34
  • This is best answer. No need to change in web.config. Upvote – Rohidas Kadam Oct 28 '16 at 10:45
  • Really love this answer. After spending a day looking at a variety of routing options in WebApi, this solution just is so simple, it is beautiful. – Aamir Mulla Jul 12 '17 at 04:47
  • 1
    @JoshM. - it's not ideal, yes. What i did was just add an 'endpoint' to the url, so instead of something like `analytes/method/2008.(w)/` i have `analytes/method/2008.(w)/detail`. That way requests with a dot in them still work and i don't have that trailing slash hanging out like it doesn't belong. Hope that helps someone. – Jamie M Sep 18 '17 at 19:45
  • 1
    Yes this is the best you shouldn't have to modify the web.config. – Timothy Gonzalez Dec 18 '17 at 04:41
  • it doesn't work for me. Maybe some breaking changes in WebAPI – VitalickS Mar 12 '19 at 10:56
116

Following setting in your web.config file should fix your issue:

<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
ataravati
  • 8,891
  • 9
  • 57
  • 89
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • I will try this out asap when I'm back at the office – Yves Schelpe Jan 08 '14 at 20:06
  • 4
    It did the trick. Though, are there any vulnerabilities by setting this option? Why isn't this the standard behaviour? – Yves Schelpe Jan 09 '14 at 06:59
  • 2
    Ok, for anyone's interest: I see my question above being answered here on the accepted answer at the time of writing (answer by Kapil Khandelwal): http://stackoverflow.com/questions/11048863/modules-runallmanagedmodulesforallrequests-true-meaning – Yves Schelpe Jan 09 '14 at 07:03
  • 2
    Yes, it worked, bus others, I would like to know which particular module is required for this functionality to work? – Greg Z. Mar 17 '14 at 20:36
  • 3
    When I tried this, it would only work if I put a '/' at the end of the path. Any suggestions why this is? On a side note, the answer below that specifically adds the 'UrlRoutingModule' instead of running all modules, also worked for me, although still with the issue that it requires a '/' at the end to work. – Nikolaj Dam Larsen Aug 05 '14 at 23:43
  • I don't need to add the "/" character for it to work.. Website's running under Windows Server 2008 R2 with IIS 7.5 – Yves Schelpe Aug 27 '14 at 09:07
  • 10
    http://stackoverflow.com/a/12151501/167018 is worth looking at if you are concerned (and rightfully so) about the performance impact of enabling runAllManagedModulesForAllRequests. – Henry C Jan 15 '15 at 16:19
  • @KiranChalla: Does this work when the app runs under classic mode? It doesn't seem so in my case. – boggy Jun 21 '16 at 20:27
  • That solved my issue. I was trying to pass an android app ID via the RESTFul api (br.com.google) and it was not working. Thanks ! – Marcello Grechi Lins Jun 30 '16 at 20:36
  • HTTP Error 500.19 - Internal Server Error The requested page cannot be accessed because the related configuration data for the page is invalid. – Toolkit Dec 15 '16 at 12:34
  • This should work, but it is a bit overkill. It should be ok for WEB API because you should not have any static resources, but I would not use it for MVC. – avidenic Nov 06 '17 at 08:17
  • Thank you. This also works for `@` at-sign character in route. – Jalal Jun 19 '18 at 08:25
  • I *also* had to add a new attribute in web.config system.web/httpRuntime: relaxedUrlToFileSystemMapping="true" – NickBeaugié Apr 18 '19 at 08:01
  • This worked AND ALSO fixed an issue where the Swagger page "Can't read swagger JSON from https://[MACHINE:PORT]/swagger/docs/v1.1" My assumption is that the v.1.1 in the URL was succumbing to the same issue. – Mike L Dec 05 '22 at 22:52
38

I've found that adding the following before the standard ExtensionlessUrlHandler solves the issue for me:

<add name="ExtensionlessUrlHandler-Integrated-4.0-ForApi"
     path="api/*"
     verb="*"
     type="System.Web.Handlers.TransferRequestHandler"
     preCondition="integratedMode,runtimeVersionv4.0" />

I don't think the name actually matters all that much except it probably helps if your IDE (Visual Studio in my case) is managing your site configuration.

H/T to https://stackoverflow.com/a/15802305/264628

Community
  • 1
  • 1
BrianS
  • 13,284
  • 15
  • 62
  • 125
25

I don't know what I am doing really, but after playing with the previous answer a bit I came up with another, perhaps more appropriate, solution:

<system.webServer>
<modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
</modules>
</system.webServer>
Greg Z.
  • 1,376
  • 14
  • 17
  • Thanks, I also don't know what it is doing but it looks better than the other solution. – Thomas Jun 23 '15 at 11:42
  • 1
    appears to move the url routing module (which is what's catching that the request includes an extension and then tries to handle it as a file) to the end of the list of modules, which is sufficient to put it after the module handling the api request. IMHO, this is the best workaround available from the ones I've seen on SO, at least [ATTOW](http://acronyms.thefreedictionary.com/ATTOW) – James Manning Oct 18 '16 at 16:33
  • 2
    It isn't the relocation to the end of the list, it's the removal of the default precondition that this module only run for managed handlers. The default config uses this format: `` – theta-fish Feb 22 '17 at 17:00
  • Thanks - that would explain the behavior. – Greg Z. Feb 23 '17 at 17:25
12

I found that I needed to do more than just set the runAllManagedModulesForAllRequests attribute to true. I also had to ensure that the extensionless URL handler was configured to look at all paths. In addition, there is one more bonus configuration setting you can add which will help in some cases. Here is my working Web.config:

<system.web>
    <httpRuntime relaxedUrlToFileSystemMapping="true" />
</system.web>
<system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <handlers>
        <remove name="WebDAV" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0"  path="*" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

Note, specifically, that the ExtensionlessUrlHandler-Integrated-4.0 has its path attribute set to * as opposed to *. (for instance).

Josh M.
  • 26,437
  • 24
  • 119
  • 200
  • 1
    I just got bit by the `path="*."`. Just curious, what is the reason people set that to `path="*."`? – JustinP8 May 17 '16 at 18:06
  • 1
    Actually, I changed it to `path="*"` and had a problem because we're hosting a documentation site side by side with our WebAPI and that site had problems with .jpg, .png, and other files with extensions. – JustinP8 May 17 '16 at 19:53
  • @JustinP8 Did you also set `` ? That should make .NET handle those static files. – Josh M. May 17 '16 at 21:25
  • 1
    Yes. I wound up doing that. I don't really like that though because now all the static files go through the .NET pipeline. Luckily, as this is a webAPI service, only the Swagger and Swashbuckle stuff for the API docs/helper site are affected. – JustinP8 May 23 '16 at 19:57
  • i found this broke all static file requests. error 500. I had runAllManaged... set to true. – Sam Nov 10 '16 at 05:09
3

I'd use this in Web.config file:

<add name="ManagedSpecialNames" path="api/people/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

before standard "ExtensionlessUrlHandler".

For instance in my case I put it here:

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ManagedFiles" path="api/people/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

So you force URLs of such pattern to be managed by you, instead of standard management as files in application directory tree.

bluish
  • 26,356
  • 27
  • 122
  • 180
2

I got stuck in this situation but appending / at the end of URL wasn't look clean for me.

so just add below in web.config handlers tag and you will be good to go.

<add name="Nancy" path="api" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" allowPathInfo="true" />
Khawaja Asim
  • 1,327
  • 18
  • 38
  • Can you explain what Nancy is and whether it is a library to be included in the project? Thanks! – bluish Nov 06 '19 at 08:50
1

I found that both way works for me: either setting runAllManagedModulesForAllRequests to true or add ExtentionlessUrlHandler as following. Finally i choose to add extensionUrLHandler since runAllManagedModulesForAllRequests do have performance impact to the site.

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <remove name="WebDAV" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*" verb="*" 
       type="System.Web.Handlers.TransferRequestHandler" 
       preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
Xuemei
  • 11
  • 1
-1

I also faced this issue. I was under a circumstance where I was not supposed to play with IIS and website config related settings. So I had to make it working by making changes at the code level only.

The point is that the most common case where you would end up having dot character in the URL is when you get some input from the user and pass it as a query string or url fragment to pass some argument to the parameters in the action method of your controller.

public class GetuserdetailsbyuseridController : ApiController
{
     string getuserdetailsbyuserid(string userId)
     {
        //some code to get user details
     }
}

Have a look at below URL where user enters his user id to get his personal details:

http://mywebsite:8080/getuserdetailsbyuserid/foo.bar

Since you have to fetch some data from the server we use http GET verb. While using GET calls any input parameters can be passed only in the URL fragments.

So to solve my problem I changed the http verb of my action to POST. Http POST verb has the facility of passing any user or non-user input in body also. So I created a JSON data and passed it into the body of the http POST request:

{
  "userid" : "foo.bar"
}

Change your method definition as below:

public class GetuserdetailsbyuseridController : ApiController
{
     [Post]
     string getuserdetailsbyuserid([FromBody] string userId)
     {
        //some code to get user details
     }
}

Note: More on when to use GET verb and when to use POST verb here.

RBT
  • 24,161
  • 21
  • 159
  • 240