Caractere ponto '.' no MVC Web API 2 para solicitações como api / people / STAFF.45287

106

O URL que estou tentando deixar funcionar é no estilo de: http://somedomain.com/api/people/staff.33311 (assim como sites como LAST.FM permitem todos os tipos de sinais em seus URLs RESTFul e WebPage , por exemplo " http://www.last.fm/artist/psy'aviah " é um url válido para LAST.FM).

O que funciona são os seguintes cenários: - http://somedomain.com/api/people/ - que retorna todas as pessoas - http://somedomain.com/api/people/staff33311 - funcionaria bem, mas não é o que eu ' m depois de querer que o url aceite um "ponto", como no exemplo abaixo - http://somedomain.com/api/people/staff.33311 - mas isso me dá um

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

Eu configurei as seguintes coisas:

  1. O controlador "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. O 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 }
        );
    }

Já tentei seguir todas as dicas desta postagem do blog http://www.hanselman.com/blog/ExperimentsInWackinessAllowingPercentsAnglebracketsAndOtherNaughtyThingsInTheASPNETIISRequestURL.aspx, mas ainda não funcionou .. Também acho que é bastante entediante e me pergunto se não há outro, maneira melhor e mais segura.

Temos nossos IDs internamente assim, então vamos ter que encontrar uma solução para encaixar o ponto de uma forma ou de outra, de preferência no estilo "." mas estou aberto a sugestões alternativas de urls, se necessário ...

Yves Schelpe
fonte
10
Não é uma resposta, mas sobre por que você está recebendo um 404 para somedomain.com/api/people/staff.33311 - por padrão, o IIS olha para este URL e vê o. como uma extensão de arquivo e invoca o manipulador de arquivo estático, ignorando sua API MVC. A resposta que você aceitou (executando todos os módulos gerenciados para todas as solicitações) funciona porque você força cada solicitação ao IIS a passar pelo pipeline do ASP.NET (portanto, seus controladores)
Henry C
Postagem intimamente relacionada aqui .
RBT

Respostas:

104

A configuração a seguir em seu web.configarquivo deve resolver o problema:

<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
Kiran Challa
fonte
4
Isso funcionou. Porém, há alguma vulnerabilidade ao definir esta opção? Por que esse não é o comportamento padrão?
Yves Schelpe
2
Ok, para o interesse de todos: vejo minha pergunta acima sendo respondida aqui com a resposta aceita no momento em que escrevo (resposta de Kapil Khandelwal): stackoverflow.com/questions/11048863/…
Yves Schelpe
2
Sim, funcionou, bus outros, gostaria de saber qual módulo específico é necessário para que essa funcionalidade funcione?
Greg Z.
3
Quando tentei fazer isso, só funcionaria se eu colocasse um '/' no final do caminho. Alguma sugestão do porquê disso? Em uma nota lateral, a resposta abaixo que adiciona especificamente o 'UrlRoutingModule' em vez de executar todos os módulos, também funcionou para mim, embora ainda com o problema de exigir um '/' no final para funcionar.
Nikolaj Dam Larsen
10
stackoverflow.com/a/12151501/167018 vale a pena examinar se você estiver preocupado (e com razão) sobre o impacto no desempenho de ativar runAllManagedModulesForAllRequests.
Henry C de
140

Sufixe o URL com uma barra, por exemplo, em http://somedomain.com/api/people/staff.33311/vez de http://somedomain.com/api/people/staff.33311.

Danny Varod
fonte
3
@AgustinMeriles minha resposta pode ser considerada mais uma solução alternativa do que uma resposta real, depende da sua interpretação da pergunta.
Danny Varod,
5
Isso não funciona quando o ponto está no final da seção da URL como em / people / member.
eYe
2
Não, e se eu não quiser uma barra no final ?! Ele deve não ser necessária.
Josh M.
1
@JoshM. Esta é uma solução alternativa, eu não escrevi ASP.NET. Além disso, a barra não afeta a solicitação. No entanto, ajuda a tornar suas intenções mais claras, como no google.com/my query goes here/v google.com/subDomain my query goes here.
Danny Varod
1
Sim, este é o melhor que você não deve ter para modificar o web.config.
Timothy Gonzalez
35

Descobri que adicionar o seguinte antes do padrão ExtensionlessUrlHandlerresolve o problema para mim:

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

Não acho que o nome realmente importe tanto, exceto que provavelmente ajuda se o seu IDE (Visual Studio no meu caso) estiver gerenciando a configuração do seu site.

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

BrianS
fonte
Eu adicionei 'depois' da linha padrão e também funcionou bem.
Jalal El-Shaer
Esta deve ser a resposta aceita. Não requer / no final de uri
daudihus
2
Obrigado pelo ANTES, porque não deu certo depois!
Mese
Eu acredito que os módulos rodam em ordem de declaração, então sempre coloque os mais específicos (ou mais importantes) antes dos mais genéricos.
BrianS
24

Não sei realmente o que estou fazendo, mas depois de brincar um pouco com a resposta anterior, encontrei outra solução, talvez mais apropriada:

<system.webServer>
<modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
</modules>
</system.webServer>
Greg Z.
fonte
Obrigado, também não sei o que está fazendo, mas parece melhor do que a outra solução.
Thomas
1
parece mover o módulo de roteamento de url (que é o que está percebendo que a solicitação inclui uma extensão e, em seguida, tenta tratá-la como um arquivo) para o final da lista de módulos, o que é suficiente para colocá-lo após o módulo que trata da solicitação da API . IMHO, esta é a melhor solução alternativa disponível das que eu vi no SO, pelo menos ATTOW
James Manning
1
Não é a realocação para o final da lista, é a remoção da pré-condição padrão de que este módulo seja executado apenas para manipuladores gerenciados. A configuração padrão usa este formato:<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler" />
theta-fish
Obrigado - isso explicaria o comportamento.
Greg Z.
8

Descobri que precisava fazer mais do que apenas definir o runAllManagedModulesForAllRequestsatributo como true. Eu também tive que garantir que o manipulador de URL sem extensão foi configurado para examinar todos os caminhos. Além disso, há mais uma definição de configuração de bônus que você pode adicionar que ajudará em alguns casos. Aqui está meu Web.config funcional:

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

Observe, especificamente, que o ExtensionlessUrlHandler-Integrated-4.0tem seu pathatributo definido *como em oposição a *.(por exemplo).

Josh M.
fonte
Acabei de ser mordido pelo path="*.". Só por curiosidade, qual é o motivo pelo qual as pessoas definem isso path="*."?
JustinP8
Na verdade, mudei para path="*"e tive um problema porque hospedamos um site de documentação lado a lado com nosso WebAPI e esse site tinha problemas com .jpg, .png e outros arquivos com extensões.
JustinP8
@ JustinP8 Você também definiu <modules runAllManagedModulesForAllRequests="true" />? Isso deve fazer o .NET lidar com esses arquivos estáticos.
Josh M.
1
Sim. Acabei fazendo isso. Eu realmente não gosto disso, porque agora todos os arquivos estáticos passam pelo pipeline do .NET. Felizmente, como este é um serviço webAPI, apenas as coisas Swagger e Swashbuckle para os documentos / site auxiliar da API são afetados.
JustinP8
Achei que isso quebrou todas as solicitações de arquivo estático. erro 500. Eu tinha runAllManaged ... definido como verdadeiro.
Sam
2

Fiquei preso nesta situação, mas anexar / no final do URL não parecia limpo para mim.

então, basta adicionar a tag web.config abaixo handlerse você estará pronto para prosseguir.

<add name="Nancy" path="api" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" allowPathInfo="true" />
Khawaja Asim
fonte
Você pode explicar o que é Nancy e se é uma biblioteca a ser incluída no projeto? Obrigado!
azulado
1

Descobri que as duas formas funcionam para mim: definir runAllManagedModulesForAllRequests como true ou adicionar ExtentionlessUrlHandler como segue. Por fim, escolho adicionar extensionUrLHandler, pois runAllManagedModulesForAllRequests tem impacto no desempenho do 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
fonte
1

Eu usaria isso no arquivo Web.config:

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

antes do "ExtensionlessUrlHandler" padrão.

Por exemplo, no meu caso, coloquei aqui:

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

Portanto, você força URLs de tal padrão a serem gerenciados por você, em vez do gerenciamento padrão como arquivos na árvore de diretório do aplicativo.

azulado
fonte
0

Eu enfrentei exatamente o mesmo problema e as circunstâncias em que eu não deveria brincar com o IIS e configurações relacionadas à configuração do site. Então eu tive que fazer isso funcionar fazendo alterações apenas no nível do código.

O ponto simples é que o caso mais comum em que você acabaria tendo um caractere ponto no URL é quando você obtém alguma entrada do usuário e a passa como uma string de consulta ou fragmento de url para passar algum argumento para os parâmetros no método de ação do seu controlador.

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

Dê uma olhada no URL abaixo onde o usuário insere seu ID de usuário para obter seus dados pessoais:

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

Uma vez que você tem que simplesmente buscar alguns dados do servidor, usamos o GETverbo http . Durante o uso de GETchamadas, qualquer parâmetro de entrada pode ser passado apenas nos fragmentos de URL.

Então, para resolver meu problema, mudei o verbo http da minha ação para POST. O POSTverbo HTTP tem a facilidade de transmitir qualquer entrada de usuário ou não usuário no corpo também. Portanto, criei um dado JSON e o passei para o corpo da POSTsolicitação http :

{
  "userid" : "foo.bar"
}

Altere a definição do seu método conforme abaixo:

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

Nota : Mais sobre quando usar GETverbo e quando usar POSTverbo aqui .

RBT
fonte
Não é uma solução apropriada se você estiver construindo uma API REST.
Mustafa Ozturk