Nombres de métodos personalizados en ASP.NET Web API


110

Estoy convirtiendo de la WCF Web API a la nueva ASP.NET MVC 4 Web API. Tengo un UsersController y quiero tener un método llamado Authenticate. Veo ejemplos de cómo hacer GetAll, GetOne, Post y Delete; sin embargo, ¿qué sucede si quiero agregar métodos adicionales a estos servicios? Por ejemplo, mi UsersService debería tener un método llamado Authenticate donde ingresan un nombre de usuario y contraseña, sin embargo, no funciona.

public class UsersController : BaseApiController
{
    public string GetAll()
    {
        return "getall!";
    }

    public string Get(int id)
    {
        return "get 1! " + id;
    }

    public User GetAuthenticate(string userName, string password, string applicationName)
    {
        LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
            userName, password, applicationName));

        //check if valid leapfrog login.
        var decodedUsername = userName.Replace("%40", "@");
        var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
        var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);

        if (leapFrogUsers.Count > 0)
        {
            return new User
            {
                Id = (uint)leapFrogUsers[0].Id,
                Guid = leapFrogUsers[0].Guid
            };
        }
        else
            throw new HttpResponseException("Invalid login credentials");
    }
}

Puedo navegar a myapi / api / users / y llamará a GetAll y puedo navegar a myapi / api / users / 1 y llamará a Get, sin embargo, si llamo a myapi / api / users / authenticate? Username = {0} & password = {1} luego llamará a Get (NO autenticar) y error:

El diccionario de parámetros contiene una entrada nula para el parámetro 'id' de tipo no anulable 'System.Int32' para el método 'System.String Get (Int32)' en 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. Un parámetro opcional debe ser un tipo de referencia, un tipo que acepte valores NULL o declararse como un parámetro opcional.

¿Cómo puedo llamar a nombres de métodos personalizados como Autenticar?


Consulte este enlace: 5ta respuesta stackoverflow.com/questions/12775590/…
Vishwa G

Respuestas:


136

De forma predeterminada, la configuración de la ruta sigue las convenciones RESTFul, lo que significa que solo aceptará los nombres de las acciones Obtener, Publicar, Poner y Eliminar (mire la ruta en global.asax => de forma predeterminada, no le permite especificar ningún nombre de acción => usa el verbo HTTP para enviar). Entonces, cuando le envía una solicitud GET /api/users/authenticate, básicamente está llamando a la Get(int id)acción y pasando, lo id=authenticateque obviamente se bloquea porque su acción Get espera un número entero.

Si desea tener nombres de acción diferentes a los estándar, puede modificar la definición de su ruta en global.asax:

Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { action = "get", id = RouteParameter.Optional }
);

Ahora puede navegar hasta /api/values/getauthenticatepara autenticar al usuario.


20
¿Hay alguna manera de hacer que siga usando Get (id), Get () Put, Delete, Post mientras se permiten otras acciones?
Shawn Mclean

@ShawnMclean Supongo que podría especificar otra ruta sin {action}que tenga una restricción {id}para que cualquier otra cosa que no sea into Guid(o lo que sea) no coincida. Entonces debería poder caer en el sugerido por Darin
Steve Greatrex

2
Una cosa más importante aquí es que con este estilo de enrutamiento, debe usar atributos para especificar los métodos HTTP permitidos (como [HttpGet]).
Himalaya Garg

1
¿Estás seguro de que necesitas usar otras acciones? ¿Realmente ha intentado adaptar lo que está haciendo dentro de las convenciones REST? No debería ser necesario utilizar otras acciones.
niico

1
@niico: Imagina que quieres tener un método Count (), que devuelve el número de elementos que devolvería Get (). No veo cómo encajar eso en Get (), Get (id), Post (...), Put (...) o Delete (id). Y, por supuesto, hay indefinidamente más métodos posibles que podría imaginar.
Jens Mander

88

Este es el mejor método que se me ha ocurrido hasta ahora para incorporar métodos GET adicionales al mismo tiempo que admite los métodos REST normales. Agregue las siguientes rutas a su WebApiConfig:

routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});

Verifiqué esta solución con la clase de prueba a continuación. Pude acceder con éxito a cada método en mi controlador a continuación:

public class TestController : ApiController
{
    public string Get()
    {
        return string.Empty;
    }

    public string Get(int id)
    {
        return string.Empty;
    }

    public string GetAll()
    {
        return string.Empty;
    }

    public void Post([FromBody]string value)
    {
    }

    public void Put(int id, [FromBody]string value)
    {
    }

    public void Delete(int id)
    {
    }
}

Verifiqué que admite las siguientes solicitudes:

GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1

Tenga en cuenta que si sus acciones GET adicionales no comienzan con 'Obtener', es posible que desee agregar un atributo HttpGet al método.


1
buena solución, ¿podría decirme si configuro los verbos puty deletecomo lo hizo en gety postfuncionará bien también?
Felipe Oriani

1
En mi opinión, esto debería incluirse en los valores predeterminados de los proyectos WebAPI (tal vez comentados). Te brinda rutas de estilo WebAPI Y MVC al mismo tiempo ...
John Culviner

1
@FelipeOriani, no creo que desee o necesite configurar puto deleteverbos, ya que esas solicitudes suelen acompañar a un parámetro de identificación para identificar el recurso al que desea aplicar esa operación. Una deletellamada a /api/foodebería arrojar un error porque ¿qué foo estás intentando eliminar? Por lo tanto, la ruta DefaultApiWithId debería manejar esos casos bien.
nwayve

4
esto no funcionó para mí en absoluto. recibí mensajes de error cuando intenté hacer un GET básico.
Matt

Para el primero, DefaultApiWithId, ¿no deberían los valores predeterminados ser nulos en lugar de nuevos {id = RouteParameter.Optional}? ¿No se requiere la 'identificación'?
Johnny Oshika

22

Llevo días en el mundo MVC4.

Por lo que vale, tengo un SitesAPIController, y necesitaba un método personalizado, que podría llamarse así:

http://localhost:9000/api/SitesAPI/Disposition/0

Con diferentes valores para el último parámetro para obtener registro con diferentes disposiciones.

Lo que finalmente funcionó para mí fue:

El método en SitesAPIController:

// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
    Site site = db.Sites.Where(s => s.Disposition == disposition).First();
    return site;
}

Y esto en WebApiConfig.cs

// this was already there
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

// this i added
config.Routes.MapHttpRoute(
    name: "Action",
    routeTemplate: "api/{controller}/{action}/{disposition}"
 );

Mientras nombraba la {disposición} como {id} me encontraba:

{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}

Cuando le cambié el nombre a {disposition}, empezó a funcionar. Entonces, aparentemente, el nombre del parámetro coincide con el valor en el marcador de posición.

Siéntase libre de editar esta respuesta para que sea más precisa / explicativa.


Gracias por el consejo. Cometí el mismo error que tú.
abhi

16

Web Api de forma predeterminada espera una URL en forma de api / {controller} / {id}, para anular este enrutamiento predeterminado. puede configurar el enrutamiento con cualquiera de las dos formas siguientes.

Primera opción:

Agregue el registro de ruta a continuación en WebApiConfig.cs

config.Routes.MapHttpRoute(
    name: "CustomApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Decora tu método de acción con HttpGet y parámetros como se muestra a continuación

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

 {

// your code here

}

para llamar a la URL del método anterior será como a continuación

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

Segunda opción Agregue el prefijo de ruta a la clase Controller y decore su método de acción con HttpGet como se muestra a continuación. En este caso, no es necesario cambiar ningún WebApiConfig.cs. Puede tener un enrutamiento predeterminado.

[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

{

// your code here

}

}

para llamar a la URL del método anterior será como a continuación

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3


Me gusta mucho la segunda opción. ¿Podrías también mostrarme cómo usarlo en VB.net? Muchas gracias.
user1617676

12

En caso de que esté usando ASP.NET 5 con ASP.NET MVC 6 , la mayoría de estas respuestas simplemente no funcionarán porque normalmente dejará que MVC cree la colección de rutas adecuada para usted (usando las convenciones RESTful predeterminadas), lo que significa que no encontrará ninguna Routes.MapRoute()llamada para editar a voluntad.

El ConfigureServices()método invocado por el Startup.csarchivo registrará MVC con el marco de inyección de dependencia integrado en ASP.NET 5: de esa manera, cuando llame ApplicationBuilder.UseMvc()más tarde en esa clase, el marco MVC agregará automáticamente estas rutas predeterminadas a su aplicación. Podemos echar un vistazo a lo que sucede detrás del capó observando la UseMvc()implementación del método dentro del código fuente del marco:

public static IApplicationBuilder UseMvc(
    [NotNull] this IApplicationBuilder app,
    [NotNull] Action<IRouteBuilder> configureRoutes)
{
    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);

    var routes = new RouteBuilder
    {
        DefaultHandler = new MvcRouteHandler(),
        ServiceProvider = app.ApplicationServices
    };

    configureRoutes(routes);

    // Adding the attribute route comes after running the user-code because
    // we want to respect any changes to the DefaultHandler.
    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
        routes.DefaultHandler,
        app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

Lo bueno de esto es que el marco ahora maneja todo el trabajo duro, iterando a través de todas las Acciones del Controlador y configurando sus rutas predeterminadas, lo que le ahorra trabajo redundante.

Lo malo es que hay poca o ninguna documentación sobre cómo podría agregar sus propias rutas. Afortunadamente, puede hacerlo fácilmente utilizando un enfoque basado en convenciones y / o basado en atributos (también conocido como enrutamiento de atributos ).

Basado en convenciones

En su clase Startup.cs, reemplace esto:

app.UseMvc();

con este:

app.UseMvc(routes =>
            {
                // Route Sample A
                routes.MapRoute(
                    name: "RouteSampleA",
                    template: "MyOwnGet",
                    defaults: new { controller = "Items", action = "Get" }
                );
                // Route Sample B
                routes.MapRoute(
                    name: "RouteSampleB",
                    template: "MyOwnPost",
                    defaults: new { controller = "Items", action = "Post" }
                );
            });

Basado en atributos

Una gran ventaja de MVC6 es que también puede definir rutas por controlador decorando la Controllerclase y / o los Actionmétodos con los parámetros apropiados RouteAttributey / o HttpGet/ HttpPostplantilla, como los siguientes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace MyNamespace.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        // GET: api/items
        [HttpGet()]
        public IEnumerable<string> Get()
        {
            return GetLatestItems();
        }

        // GET: api/items/5
        [HttpGet("{num}")]
        public IEnumerable<string> Get(int num)
        {
            return GetLatestItems(5);
        }       

        // GET: api/items/GetLatestItems
        [HttpGet("GetLatestItems")]
        public IEnumerable<string> GetLatestItems()
        {
            return GetLatestItems(5);
        }

        // GET api/items/GetLatestItems/5
        [HttpGet("GetLatestItems/{num}")]
        public IEnumerable<string> GetLatestItems(int num)
        {
            return new string[] { "test", "test2" };
        }

        // POST: /api/items/PostSomething
        [HttpPost("PostSomething")]
        public IActionResult Post([FromBody]string someData)
        {
            return Content("OK, got it!");
        }
    }
}

Este controlador manejará las siguientes solicitudes:

 [GET] api/items
 [GET] api/items/5
 [GET] api/items/GetLatestItems
 [GET] api/items/GetLatestItems/5
 [POST] api/items/PostSomething

También observe que si usa los dos enfoques juntos, las rutas basadas en atributos (cuando estén definidas) anularán las basadas en la Convención, y ambas anularán las rutas predeterminadas definidas por UseMvc().

Para obtener más información, también puede leer la siguiente publicación en mi blog.


1
¡Esto es perfecto! Ninguna de las otras respuestas realmente hizo lo que necesitaba. Pero me salvaste :)
Rey Arturo III

¿Existe alguna forma de utilizar un modelo predefinido como segundo parámetro? Por ejemplo, cuando estoy remendar un determinado usuario de esta manera: public IActionResult Patch(int id, [FromQuery] Person person), todas las propiedades de entrada son nulas!
Rey Arturo III


0

Simplemente modifique su WebAPIConfig.cs como se muestra a continuación

Routes.MapHttpRoute(
  name: "DefaultApi",
  routeTemplate: "api/{controller}/{action}/{id}",
  defaults: new { action = "get", id = RouteParameter.Optional });

Luego implemente su API como se muestra a continuación

    // GET: api/Controller_Name/Show/1
    [ActionName("Show")]
    [HttpGet]
    public EventPlanner Id(int id){}

0

Web APi 2 y versiones posteriores admiten un nuevo tipo de enrutamiento, llamado enrutamiento de atributos. Como su nombre lo indica, el enrutamiento de atributos usa atributos para definir rutas. El enrutamiento de atributos le brinda más control sobre los URI en su API web. Por ejemplo, puede crear fácilmente URI que describan jerarquías de recursos.

Por ejemplo:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

Perfeccionará y no necesita ningún código adicional, por ejemplo, en WebApiConfig.cs. Solo debe asegurarse de que el enrutamiento de la API web esté habilitado o no en WebApiConfig.cs, de lo contrario, puede activarlo como se muestra a continuación:

        // Web API routes
        config.MapHttpAttributeRoutes();

No tiene que hacer nada más o cambiar algo en WebApiConfig.cs. Para obtener más detalles, puede consultar este artículo .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.