c# - pueden - se encontraron varias acciones que coincidían con la solicitud post
Controlador único con múltiples métodos GET en ASP.NET Web API (14)
¿Has intentado cambiar a WebInvokeAttribute y configurar el método para "GET"?
Creo que tuve un problema similar y pasé a decir explícitamente qué método (GET / PUT / POST / DELETE) se espera en la mayoría, si no en todos, mis métodos.
public class SomeController : ApiController
{
[WebInvoke(UriTemplate = "{itemSource}/Items"), Method="GET"]
public SomeValue GetItems(CustomParam parameter) { ... }
[WebInvoke(UriTemplate = "{itemSource}/Items/{parent}", Method = "GET")]
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
WebGet debe manejarlo, pero he visto que tiene algunos problemas con múltiples Obtenga mucho menos Get múltiple del mismo tipo de devolución.
[Editar: nada de esto es válido con la puesta de sol de WCF WebAPI y la migración a ASP.Net WebAPI en la pila de MVC]
En la API web tuve una clase de estructura similar:
public class SomeController : ApiController
{
[WebGet(UriTemplate = "{itemSource}/Items")]
public SomeValue GetItems(CustomParam parameter) { ... }
[WebGet(UriTemplate = "{itemSource}/Items/{parent}")]
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
Como pudimos mapear métodos individuales, fue muy simple obtener la solicitud correcta en el lugar correcto. Para una clase similar que solo tenía un único método GET
pero también tenía un parámetro Object
, utilicé con éxito IActionValueBinder
. Sin embargo, en el caso descrito anteriormente, aparece el siguiente error:
Multiple actions were found that match the request:
SomeValue GetItems(CustomParam parameter) on type SomeType
SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType
Estoy intentando abordar este problema anulando el método ApiController
de ApiController
pero sin suerte hasta el momento. ¿Algún consejo sobre este tema?
Editar: Olvidé mencionar que ahora estoy tratando de mover este código en ASP.NET Web API, que tiene un enfoque diferente al enrutamiento. La pregunta es, ¿cómo hago que el código funcione en ASP.NET Web API?
Con el nuevo Web Api 2 se ha vuelto más fácil tener múltiples métodos de obtención.
Si el parámetro pasado a los métodos GET
es lo suficientemente diferente para que el sistema de enrutamiento de atributos distinga sus tipos, como es el caso con int
s y Guid
s, puede especificar el tipo esperado en el atributo [Route...]
Por ejemplo -
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
// GET api/values/7
[Route("{id:int}")]
public string Get(int id)
{
return $"You entered an int - {id}";
}
// GET api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D
[Route("{id:Guid}")]
public string Get(Guid id)
{
return $"You entered a GUID - {id}";
}
}
Para obtener más detalles sobre este enfoque, consulte aquí http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/
Otra opción es dar a los métodos GET
diferentes rutas.
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
public string Get()
{
return "simple get";
}
[Route("geta")]
public string GetA()
{
return "A";
}
[Route("getb")]
public string GetB()
{
return "B";
}
}
Consulte aquí para obtener más detalles: http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/
En ASP.NET Core 2.0 puede agregar el atributo Ruta al controlador:
[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
public SomeValue GetItems(CustomParam parameter) { ... }
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
Encuentro que los atributos deben ser más limpios que agregarlos manualmente a través de un código. Aquí hay un ejemplo simple.
[RoutePrefix("api/example")]
public class ExampleController : ApiController
{
[HttpGet]
[Route("get1/{param1}")] // /api/example/get1/1?param2=4
public IHttpActionResult Get(int param1, int param2)
{
Object example = null;
return Ok(example);
}
}
También necesita esto en su webapiconfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Algunos buenos enlaces http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Éste explica mejor el enrutamiento. http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Esta es la mejor manera que he encontrado para soportar métodos GET adicionales y también soporto 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 golpear exitosamente 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'', puede agregar un atributo HttpGet al método.
Intentaba utilizar el enrutamiento de atributos de Web Api 2 para permitir varios métodos de obtención, y había incorporado las sugerencias útiles de respuestas anteriores, pero en el controlador solo había decorado el método "especial" (ejemplo):
[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {
... sin colocar también un [RoutePrefix] en la parte superior del controlador:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
Recibí errores que indicaban que no se encontró ninguna ruta que coincida con el URI enviado. Una vez que tuve tanto [Route] decorando el método como [RoutePrefix] decorando el Controller como un todo, funcionó.
Modifique el WebApiConfig y agregue al final otro Routes.MapHttpRoute como este:
config.Routes.MapHttpRoute(
name: "ServiceApi",
routeTemplate: "api/Service/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Luego crea un controlador como este:
public class ServiceController : ApiController
{
[HttpGet]
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IQueryable<DropDownModel> DropDowEmpresa()
{
return db.Empresa.Where(x => x.Activo == true).Select(y =>
new DropDownModel
{
Id = y.Id,
Value = y.Nombre,
});
}
[HttpGet]
public IQueryable<DropDownModel> DropDowTipoContacto()
{
return db.TipoContacto.Select(y =>
new DropDownModel
{
Id = y.Id,
Value = y.Nombre,
});
}
[HttpGet]
public string FindProductsByName()
{
return "FindProductsByName";
}
}
Así es como lo resolví. Espero que ayude a alguien.
Necesita definir más rutas en global.asax.cs como esta:
routes.MapHttpRoute(
name: "Api with action",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Ninguno de los ejemplos anteriores funcionó para mis necesidades personales. El siguiente es lo que terminé haciendo.
public class ContainsConstraint : IHttpRouteConstraint
{
public string[] array { get; set; }
public bool match { get; set; }
/// <summary>
/// Check if param contains any of values listed in array.
/// </summary>
/// <param name="param">The param to test.</param>
/// <param name="array">The items to compare against.</param>
/// <param name="match">Whether we are matching or NOT matching.</param>
public ContainsConstraint(string[] array, bool match)
{
this.array = array;
this.match = match;
}
public bool Match(System.Net.Http.HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (values == null) // shouldn''t ever hit this.
return true;
if (!values.ContainsKey(parameterName)) // make sure the parameter is there.
return true;
if (string.IsNullOrEmpty(values[parameterName].ToString())) // if the param key is empty in this case "action" add the method so it doesn''t hit other methods like "GetStatus"
values[parameterName] = request.Method.ToString();
bool contains = array.Contains(values[parameterName]); // this is an extension but all we are doing here is check if string array contains value you can create exten like this or use LINQ or whatever u like.
if (contains == match) // checking if we want it to match or we don''t want it to match
return true;
return false;
}
Para usar lo anterior en su ruta use:
config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { action = RouteParameter.Optional, id = RouteParameter.Optional}, new { action = new ContainsConstraint( new string[] { "GET", "PUT", "DELETE", "POST" }, true) });
Lo que sucede es el tipo de restricción de falsificaciones en el método, de modo que esta ruta solo coincidirá con los métodos predeterminados GET, POST, PUT y DELETE. El "verdadero" dice que queremos verificar si hay una coincidencia de los elementos en la matriz. Si fuera falso, estaría diciendo excluir los que están en la línea. Puede usar rutas por encima de este método predeterminado como:
config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });
En lo anterior, básicamente busca la siguiente URL => http://www.domain.com/Account/Status/Active
o algo así.
Más allá de lo anterior, no estoy seguro de que me vuelva loco. Al final del día debe ser por recurso. Pero sí veo la necesidad de asignar URLs amigables por varias razones. Me siento bastante seguro de que a medida que Web Api evolucione habrá algún tipo de provisión. Si es hora, construiré una solución más permanente y la publicaré.
No estoy seguro de haber encontrado la respuesta, pero lo hice y funciona
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/values/5
public string Get(int id)
{
return "value";
}
// GET /api/values/5
[HttpGet]
public string GetByFamily()
{
return "Family value";
}
Ahora en global.asx
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
No pude hacer funcionar ninguna de las soluciones de enrutamiento anteriores, parece que algo de la sintaxis ha cambiado y aún soy nuevo en MVC, aunque puse en marcha este truco realmente horrible (y simple) que me va a hacer por ahora - nota, esto reemplaza el método "myObject GetMyObjects (Id. larga) público" - cambiamos el tipo de "id" a una cadena y cambiamos el tipo de devolución a objeto.
// GET api/MyObjects/5
// GET api/MyObjects/function
public object GetMyObjects(string id)
{
id = (id ?? "").Trim();
// Check to see if "id" is equal to a "command" we support
// and return alternate data.
if (string.Equals(id, "count", StringComparison.OrdinalIgnoreCase))
{
return db.MyObjects.LongCount();
}
// We now return you back to your regularly scheduled
// web service handler (more or less)
var myObject = db.MyObjects.Find(long.Parse(id));
if (myObject == null)
{
throw new HttpResponseException
(
Request.CreateResponse(HttpStatusCode.NotFound)
);
}
return myObject;
}
Si tiene varias acciones dentro del mismo archivo, pase el mismo argumento, por ejemplo, Id a todas las acciones. Esto se debe a que la acción solo puede identificar a Id, por lo tanto, en lugar de dar ningún nombre al argumento, solo declarar Id así.
[httpget]
[ActionName("firstAction")] firstAction(string Id)
{.....
.....
}
[httpget]
[ActionName("secondAction")] secondAction(Int Id)
{.....
.....
}
//Now go to webroute.config file under App-start folder and add following
routes.MapHttpRoute(
name: "firstAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "secondAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Ve de esto:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
A esto:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Por lo tanto, ahora puede especificar a qué acción (método) desea enviar su solicitud HTTP.
publicar en "http: // localhost: 8383 / api / Command / PostCreateUser" invoca:
public bool PostCreateUser(CreateUserCommand command)
{
//* ... *//
return true;
}
y publicar en "http: // localhost: 8383 / api / Command / PostMakeBooking" invoca:
public bool PostMakeBooking(MakeBookingCommand command)
{
//* ... *//
return true;
}
Intenté esto en una aplicación de servicio de API WEB hospedada por mí mismo y funciona como un encanto :)
Alternativa simple
Solo usa una cadena de consulta.
Enrutamiento
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Controlador
public class TestController : ApiController
{
public IEnumerable<SomeViewModel> Get()
{
}
public SomeViewModel GetById(int objectId)
{
}
}
Peticiones
GET /Test
GET /Test?objectId=1
Nota
Tenga en cuenta que la cadena de consulta param no debe ser "id" ni lo que sea que el parámetro esté en la ruta configurada.