Znak kropki „.” w MVC Web API 2 dla żądań, takich jak api / people / STAFF.45287

106

Adres URL, który próbuję udostępnić, jest w stylu: http://somedomain.com/api/people/staff.33311 (podobnie jak witryny LAST.FM zezwalają na wszelkiego rodzaju znaki w swoich adresach URL RESTFul & WebPage , na przykład „ http://www.last.fm/artist/psy'aviah ” to prawidłowy adres URL LAST.FM).

To, co działa, to następujące scenariusze: - http://somedomain.com/api/people/ - który zwraca wszystkich ludzi - http://somedomain.com/api/people/staff33311 - również by działał, ale to nie jest to, co ja ” m po tym, jak chciałbym, aby adres URL akceptował „kropkę”, jak w przykładzie poniżej - http://somedomain.com/api/people/staff.33311 - ale to daje mi

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

Skonfigurowałem następujące rzeczy:

  1. Kontroler „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. Plik 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 }
        );
    }
    

Próbowałem już postępować zgodnie ze wszystkimi wskazówkami z tego wpisu na blogu http://www.hanselman.com/blog/ExperimentsInWackinessAllowingPercentsAnglebracketsAndOtherNaughtyThingsInTheASPNETIISRequestURL.aspx, ale nadal nie działa. Myślę też, że to dość żmudne i zastanawiam się, czy nie ma innego, lepszy i bezpieczniejszy sposób.

Mamy wewnętrzne Id, więc będziemy musieli znaleźć rozwiązanie, które pozwoli dopasować kropkę w taki czy inny sposób, najlepiej w stylu „”. ale jestem otwarty na alternatywne sugestie dotyczące adresów URL, jeśli zajdzie taka potrzeba ...

Yves Schelpe
źródło
10
Nie jest to odpowiedź, ale dlaczego otrzymujesz 404 dla somedomain.com/api/people/staff.33311 - domyślnie IIS patrzy na ten adres URL i widzi. jako rozszerzenie pliku i wywołuje statyczną procedurę obsługi plików, pomijając interfejs API MVC. Zaakceptowana odpowiedź (uruchomienie wszystkich zarządzanych modułów dla wszystkich żądań) działa, ponieważ wymuszasz, aby każde żądanie skierowane do usług IIS przechodziło przez potok ASP.NET (w związku z tym kontrolery)
Henry C
Blisko powiązany post tutaj .
RBT

Odpowiedzi:

104

Poniższe ustawienie w web.configpliku powinno rozwiązać problem:

<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
Kiran Challa
źródło
4
To załatwiło sprawę. Chociaż czy są jakieś luki w zabezpieczeniach, ustawiając tę ​​opcję? Dlaczego nie jest to standardowe zachowanie?
Yves Schelpe,
2
Ok, dla każdego: widzę, że odpowiedź na moje pytanie powyżej jest tutaj, na temat odpowiedzi zaakceptowanej w momencie pisania (odpowiedź od Kapila Khandelwal): stackoverflow.com/questions/11048863/…
Yves Schelpe
2
Tak, zadziałało, inne autobusy, chciałbym wiedzieć, który konkretny moduł jest wymagany, aby ta funkcjonalność działała?
Greg Z.
3
Kiedy próbowałem tego, zadziałałoby tylko wtedy, gdy wstawię „/” na końcu ścieżki. Jakieś sugestie, dlaczego tak jest? Na marginesie, odpowiedź poniżej, która konkretnie dodaje „UrlRoutingModule” zamiast uruchamiania wszystkich modułów, również działała dla mnie, chociaż nadal występuje problem, że wymaga znaku „/” na końcu do działania.
Nikolaj Dam Larsen,
10
stackoverflow.com/a/12151501/167018 jest wart uwagi, jeśli obawiasz się (i słusznie) o wpływ włączenia runAllManagedModulesForAllRequests na wydajność.
Henry C
140

Dodaj sufiks adresu URL ukośnikiem, np. http://somedomain.com/api/people/staff.33311/Zamiast http://somedomain.com/api/people/staff.33311.

Danny Varod
źródło
3
@AgustinMeriles moja odpowiedź może być uważana za bardziej obejście niż rzeczywistą odpowiedź, zależy od twojej interpretacji pytania.
Danny Varod
5
To jednak nie działa, gdy kropka znajduje się na końcu sekcji adresu URL, jak w / people / member.
eYe
2
Nie, a co jeśli nie chcę cięcia na końcu ?! Nie powinno to być wymagane.
Josh M.
1
@JoshM. To jest obejście, nie napisałem ASP.NET. Ukośnik nie wpływa również na żądanie. To jednak, pomaga uczynić twoje intencje bardziej jasne jak w google.com/my query goes here/wersetach google.com/subDomain my query goes here.
Danny Varod,
1
Tak, to najlepsze rozwiązanie, którego nie powinieneś musieć modyfikować w pliku web.config.
Timothy Gonzalez
35

Zauważyłem, że dodanie następujących elementów przed standardem ExtensionlessUrlHandlerrozwiązuje problem:

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

Nie sądzę, że nazwa ma tak duże znaczenie, z wyjątkiem tego, że prawdopodobnie pomaga, jeśli twoje IDE (w moim przypadku Visual Studio) zarządza konfiguracją witryny.

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

BrianS
źródło
Dodałem go „po” standardowej linii i też działało dobrze.
Jalal El-Shaer
To powinna być akceptowana odpowiedź. Nie wymaga / pod koniec uri
daudihus
2
Dzięki za PRZED, ponieważ nie działało później!
Mese
Uważam, że moduły są uruchamiane w kolejności deklaracji, więc zawsze umieszczaj bardziej szczegółowe (lub ważniejsze) przed bardziej ogólnymi.
BrianS
24

Nie wiem, co naprawdę robię, ale po zabawie z poprzednią odpowiedzią wpadłem na inne, być może bardziej odpowiednie rozwiązanie:

<system.webServer>
<modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
</modules>
</system.webServer>
Greg Z.
źródło
Dzięki, ja też nie wiem, co robi, ale wygląda lepiej niż inne rozwiązanie.
Thomas
1
wydaje się, że przenosi moduł routingu adresu URL (który łapie, że żądanie zawiera rozszerzenie, a następnie próbuje obsłużyć je jako plik) na koniec listy modułów, co wystarczy, aby umieścić go za modułem obsługującym żądanie API . IMHO, jest to najlepsze obejście dostępne spośród tych, które widziałem w SO, przynajmniej ATTOW
James Manning
1
To nie jest relokacja na koniec listy, jest to usunięcie domyślnego warunku wstępnego, że ten moduł działa tylko dla zarządzanych programów obsługi. Domyślna konfiguracja używa tego formatu:<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler" />
theta-fish
Dzięki - to by wyjaśniało zachowanie.
Greg Z.
8

Okazało się, że muszę zrobić coś więcej niż tylko ustawić runAllManagedModulesForAllRequestsatrybut na true. Musiałem również upewnić się, że program obsługi adresów URL bez rozszerzeń został skonfigurowany do przeglądania wszystkich ścieżek. Ponadto istnieje jeszcze jedno dodatkowe ustawienie konfiguracji, które możesz dodać, co w niektórych przypadkach pomoże. Oto mój działający plik 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>

Uwaga, w szczególności, że ExtensionlessUrlHandler-Integrated-4.0ma pathzestaw atrybutów się *, w przeciwieństwie do *.(na przykład).

Josh M.
źródło
Właśnie zostałem ugryziony path="*.". Ciekawe, jaki jest powód, dla którego ludzie to ustawili path="*."?
JustinP8
Właściwie zmieniłem to na path="*"i miałem problem, ponieważ hostujemy witrynę z dokumentacją obok naszego interfejsu WebAPI, a ta witryna miała problemy z plikami .jpg, .png i innymi plikami z rozszerzeniami.
JustinP8
@ JustinP8 Czy też ustawiłeś <modules runAllManagedModulesForAllRequests="true" />? To powinno sprawić, że .NET obsłuży te pliki statyczne.
Josh M.
1
Tak. Skończyło się na tym, że to zrobiłem. Nie podoba mi się to jednak, ponieważ teraz wszystkie pliki statyczne przechodzą przez potok .NET. Na szczęście, ponieważ jest to usługa webAPI, dotyczy to tylko rzeczy Swagger i Swashbuckle dla strony dokumentacji / pomocniczej API.
JustinP8
stwierdziłem, że to zepsuło wszystkie statyczne żądania plików. błąd 500. Uruchomiłem runAllManaged ... ustawiono na true.
Sam
2

Utknąłem w tej sytuacji, ale dołączanie / na końcu adresu URL nie wyglądało na czyste.

więc po prostu dodaj poniżej w tagu web.config handlers i będziesz gotowy .

<add name="Nancy" path="api" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" allowPathInfo="true" />
Khawaja Asim
źródło
Czy możesz wyjaśnić, czym jest Nancy i czy jest to biblioteka, którą należy uwzględnić w projekcie? Dzięki!
niebieskawe
1

Okazało się, że oba sposoby działają dla mnie: albo ustawiając runAllManagedModulesForAllRequests na true, albo dodaj ExtentionlessUrlHandler w następujący sposób. Wreszcie zdecydowałem się dodać extensionUrLHandler, ponieważ runAllManagedModulesForAllRequests mają wpływ na wydajność witryny.

<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
źródło
1

Użyłbym tego w pliku Web.config:

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

przed standardem „ExtensionlessUrlHandler”.

Na przykład w moim przypadku umieściłem to tutaj:

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

Dlatego wymuszasz, aby adresy URL o takim wzorcu były zarządzane przez Ciebie, zamiast standardowego zarządzania plikami w drzewie katalogów aplikacji.

niebieskawy
źródło
0

Zmierzyłem się z tym samym problemem i okolicznościami, w których nie miałem grać z IIS i ustawieniami związanymi z konfiguracją witryny. Musiałem więc sprawić, by działało, wprowadzając zmiany tylko na poziomie kodu.

Prosta sprawa jest taka, że ​​najczęstszym przypadkiem, w którym kończy się znak kropki w adresie URL, jest otrzymanie danych od użytkownika i przekazanie ich jako ciągu zapytania lub fragmentu adresu URL, aby przekazać jakiś argument do parametrów w metodzie akcji kontrolera.

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

Spójrz na poniższy adres URL, pod którym użytkownik wprowadza swój identyfikator użytkownika, aby uzyskać swoje dane osobowe:

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

Ponieważ musisz po prostu pobrać dane z serwera, używamy GETczasownika http . Podczas korzystania z GETwywołań wszelkie parametry wejściowe mogą być przekazywane tylko we fragmentach adresu URL.

Aby rozwiązać problem, zmieniłem czasownik http mojej czynności na POST. POSTCzasownik HTTP ma również możliwość przekazywania do treści danych wejściowych użytkownika lub nie-użytkownika. Utworzyłem więc dane JSON i przesłałem je do treści POSTżądania http :

{
  "userid" : "foo.bar"
}

Zmień definicję metody, jak poniżej:

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

Uwaga : więcej o tym, kiedy używać GETczasownika, a kiedy POSTczasownika, tutaj .

RBT
źródło
Nie jest to odpowiednie rozwiązanie, jeśli tworzysz REST API.
Mustafa Ozturk