web-dev-qa-db-fra.com

Contrôleur unique avec plusieurs méthodes GET dans l'API Web ASP.NET

Dans Web API, j'avais une classe de structure similaire:

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) { ... }
}

Comme nous pouvions mapper des méthodes individuelles, il était très simple d’obtenir la bonne demande au bon endroit. Pour une classe similaire qui n'avait qu'une seule méthode GET mais qui avait également un paramètre Object, j'ai utilisé avec succès IActionValueBinder. Cependant, dans le cas décrit ci-dessus, l'erreur suivante apparaît:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType

J'essaie d'aborder ce problème en ignorant la méthode ExecuteAsync de ApiController, mais sans succès jusqu'à présent. Un conseil sur cette question?

Edit: j’ai oublié de mentionner que j’essaie maintenant de déplacer ce code sur l’API Web ASP.NET, qui utilise une approche différente du routage. La question est de savoir comment faire fonctionner le code sur les API Web ASP.NET.

151
paulius_l

C’est le meilleur moyen que j’ai trouvé de prendre en charge des méthodes GET supplémentaires et les méthodes normales REST. Ajoutez les itinéraires suivants à votre 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)});

J'ai vérifié cette solution avec la classe de test ci-dessous. J'ai réussi à frapper chaque méthode dans mon contrôleur ci-dessous:

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)
    {
    }
}

J'ai vérifié qu'il prend en charge les demandes suivantes:

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

Note Que si vos actions GET supplémentaires ne commencent pas par 'Get', vous voudrez peut-être ajouter un attribut HttpGet à la méthode.

239
sky-dev

Partir de ça:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
            new { id = RouteParameter.Optional });

Pour ça:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
            new { id = RouteParameter.Optional });

Par conséquent, vous pouvez maintenant spécifier l'action (méthode) à laquelle vous souhaitez envoyer votre requête HTTP.

publier sur "http: // localhost: 8383/api/Command/PostCreateUser" appelle:

public bool PostCreateUser(CreateUserCommand command)
{
    //* ... *//
    return true;
}

et publier sur "http: // localhost: 8383/api/Command/PostMakeBooking" invoque:

public bool PostMakeBooking(MakeBookingCommand command)
{
    //* ... *//
    return true;
}

J'ai essayé cela dans une application de service API Web auto-hébergée et cela fonctionne comme un charme :)

55
uggeh

Je trouve les attributs plus propres à utiliser que de les ajouter manuellement via un code. Voici un exemple 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);
    }

}

Vous en avez aussi besoin dans votre 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 }
);

Quelques bons liens http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Celui-ci explique routage mieux. http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api

29
Kalel Wade

Vous devez définir d'autres itinéraires dans global.asax.cs comme ceci:

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 }
);
11
Alexander Zeitler

Avec la nouvelle version de Web Api 2, il est devenu plus facile d’avoir plusieurs méthodes get.

Si le paramètre transmis aux méthodes GET est suffisamment différent pour que le système de routage d'attributs puisse distinguer leurs types, comme c'est le cas avec ints et Guids, vous pouvez spécifier le type attendu dans le [Route...] attribut

Par exemple -

[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}";
    }
} 

Pour plus de détails sur cette approche, voir ici http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

Une autre option consiste à donner aux méthodes GET différents itinéraires.

    [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";
        }
   }

Voir ici pour plus de détails - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/

6
Bryan

Dans ASP.NET Core 2.0, vous pouvez ajouter un attribut Route au contrôleur:

[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
    public SomeValue GetItems(CustomParam parameter) { ... }

    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
3
maskalek

Je ne sais pas si vous avez trouvé la réponse, mais je l'ai fait et cela fonctionne

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";
}

Maintenant dans 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 }
);
3
Pavan Josyula

Avez-vous essayé de passer à WebInvokeAttribute et de définir la méthode sur "GET"?

Je crois que j'ai eu un problème similaire et est passé à indiquer explicitement quelle méthode (GET/PUT/POST/DELETE) est attendue sur la plupart, sinon toutes, mes méthodes.

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) { ... }
}

Le WebGet devrait le gérer mais je l'ai vu rencontrer quelques problèmes avec plusieurs Get beaucoup moins multiples Obtenez le même type de retour.

[Edit: rien de cela n'est valide avec la temporisation de WCF WebAPI et la migration vers ASP.Net WebAPI sur la pile MVC]

3
PMontgomery

J'essayais d'utiliser le routage d'attributs Web Api 2 pour permettre plusieurs méthodes Get, et j'avais incorporé les suggestions utiles des réponses précédentes, mais dans le contrôleur, je n'avais décoré que la méthode "spéciale" (exemple):

[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {

... sans placer également un [RoutePrefix] en haut du contrôleur:

[RoutePrefix("api/values")]
public class ValuesController : ApiController

Je recevais des erreurs indiquant qu'aucune route ne correspondait à l'URI soumis. Une fois, [Route] décorant la méthode et [RoutePrefix] décorant le contrôleur dans son ensemble, cela a fonctionné.

2

Impossible de faire fonctionner l'une des solutions de routage ci-dessus - une partie de la syntaxe semble avoir changé et je suis encore novice dans MVC - à la rigueur, bien que j'aie mis en place ce bidouillage vraiment horrible (et simple) qui me fera comprendre by for now - remarque, ceci remplace la méthode "public MyObject GetMyObjects (long id)" - nous changeons le type de "id" en une chaîne et changeons le type de retour en object.

// 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;
}
0
BrainSlugs83

Si vous avez plusieurs actions dans le même fichier, passez le même argument, par exemple. Id à toutes les actions. En effet, seule une action peut identifier un identifiant. Ainsi, au lieu de donner un nom à un argument, déclarez uniquement Id de cette manière.


[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 }
);
0
Uttam Kumar

Aucun des exemples ci-dessus n'a fonctionné pour mes besoins personnels. Le ci-dessous est ce que j'ai fini par faire.

 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;             

    }

Pour utiliser ce qui précède dans votre itinéraire, utilisez:

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) });

Ce qui se passe est le type de contrainte simulé dans la méthode, de sorte que cette route ne correspond qu'aux méthodes par défaut GET, POST, PUT et DELETE. Le "vrai" indique que nous voulons vérifier la correspondance des éléments du tableau. Si c'était faux, vous diriez d'exclure ceux de la chaîne. Vous pouvez alors utiliser des routes au-dessus de cette méthode par défaut comme:

config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });

Dans ce qui précède, vous recherchez essentiellement l'URL suivante => http://www.domain.com/Account/Status/Active ou quelque chose du genre.

Au-delà de ce qui précède, je ne suis pas sûr de devenir trop fou. À la fin de la journée, cela devrait être par ressource. Mais je vois un besoin de mapper des urls amicales pour diverses raisons. Je suis assez certain que Web Api évoluera, il y aura une sorte de provision. Si le temps le permet, je construirai une solution plus permanente et posterai.

0
origin1tech

Alternative simple

Utilisez simplement une chaîne de requête.

Routing

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

contrôleur

public class TestController : ApiController
{
    public IEnumerable<SomeViewModel> Get()
    {
    }

    public SomeViewModel GetById(int objectId)
    {
    }
}

demandes

GET /Test
GET /Test?objectId=1

Note

Gardez à l'esprit que le paramètre de chaîne de requête ne doit pas être "id" ni quel que soit le paramètre figurant dans la route configurée.

0
Seth Flowers