Einzelner Controller mit mehreren GET-Methoden in der ASP.NET-Web-API

167

In der Web-API hatte ich eine Klasse mit ähnlicher Struktur:

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

Da wir einzelne Methoden abbilden konnten, war es sehr einfach, die richtige Anfrage am richtigen Ort zu erhalten. Für eine ähnliche Klasse, die nur eine einzige GETMethode, aber auch einen ObjectParameter hatte, habe ich erfolgreich verwendet IActionValueBinder. In dem oben beschriebenen Fall erhalte ich jedoch den folgenden Fehler:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

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

Ich versuche, dieses Problem anzugehen, indem ich die ExecuteAsyncMethode überschreibe , ApiControlleraber bisher ohne Glück. Irgendwelche Ratschläge zu diesem Thema?

Bearbeiten: Ich habe vergessen zu erwähnen, dass ich jetzt versuche, diesen Code auf der ASP.NET-Web-API zu verschieben, die einen anderen Ansatz für das Routing hat. Die Frage ist, wie der Code auf der ASP.NET-Web-API funktioniert.

paulius_l
quelle
1
Haben Sie noch das {Eltern} als RouteParameter.Optional?
Antony Scott
Ja, habe ich. Vielleicht verwende ich den IActionValueBinder falsch, weil er für Typen wie int id (wie in der Demo) gut funktioniert.
Paulius_l
Entschuldigung, ich hätte klarer sein sollen. Ich hätte gedacht, dass eine optionale Option sowohl der Artikelroute als auch der Unterartikelroute entspricht, was die angezeigte Fehlermeldung erklären würde.
Antony Scott
Wir haben derzeit die Diskussion, ob die folgenden Ansätze (mit mehreren Routen) gegen die richtigen REST-Regeln verstoßen. Meiner Meinung nach ist das in Ordnung. Mein Kollege findet es nicht schön. Irgendwelche Kommentare dazu?
Remy
Ich war generell dagegen, als ich anfing, über REST zu lesen. Ich bin mir immer noch nicht sicher, ob dies ein angemessener Ansatz ist, aber manchmal ist er bequemer oder benutzerfreundlicher, so dass ein leichtes Biegen der Regeln möglicherweise nicht so schlecht ist. Solange es funktioniert, um ein bestimmtes Problem zu lösen. Es sind bereits 6 Monate vergangen, seit ich diese Frage gestellt habe, und wir haben es seitdem nicht bereut, diesen Ansatz verwendet zu haben.
Paulius_l

Antworten:

249

Dies ist der beste Weg, um zusätzliche GET-Methoden und auch die normalen REST-Methoden zu unterstützen. Fügen Sie Ihrer WebApiConfig die folgenden Routen hinzu:

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

Ich habe diese Lösung mit der folgenden Testklasse überprüft. Ich konnte jede der folgenden Methoden in meinem Controller erfolgreich ausführen:

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

Ich habe überprüft, ob die folgenden Anforderungen unterstützt werden:

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

Hinweis: Wenn Ihre zusätzlichen GET-Aktionen nicht mit 'Get' beginnen, möchten Sie der Methode möglicherweise ein HttpGet-Attribut hinzufügen.

sky-dev
quelle
4
Dies ist eine großartige Antwort und hat mir bei einer anderen verwandten Frage sehr geholfen. Vielen Dank!!
Alfero Chingono
4
Versuchte dies - scheint nicht zu funktionieren. Die Routen werden alle zufällig der GetBlah-Methode (lange ID) zugeordnet. :(
BrainSlugs83
1
@ BrainSlugs83: Das hängt von der Reihenfolge ab. Und Sie möchten (zu den "withId" -Methoden) aconstraints: new{id=@"\d+"}
Eric Falsken
4
Wie wäre es mit einer weiteren Methode - Get (int id, string name)? ... es schlägt fehl
Anil Purswani
1
Ich musste eine zusätzliche Route wie diese routes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});für meine PutMethode hinzufügen , sonst gab es mir 404.
Syed Ali Taqi
57

Gehen Sie von diesem:

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

Dazu:

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

Daher können Sie jetzt angeben, an welche Aktion (Methode) Sie Ihre HTTP-Anfrage senden möchten.

Das Posten in "http: // localhost: 8383 / api / Command / PostCreateUser" ruft Folgendes auf:

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

und das Posten in "http: // localhost: 8383 / api / Command / PostMakeBooking" ruft Folgendes auf:

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

Ich habe dies in einer selbst gehosteten WEB API-Dienstanwendung versucht und es funktioniert wie ein Zauber :)

uggeh
quelle
8
Danke für die hilfreiche Antwort. Ich möchte hinzufügen, dass, wenn Sie Ihre Methodennamen mit Get, Post usw. beginnen, Ihre Anforderungen diesen Methoden basierend auf dem verwendeten HTTP-Verb zugeordnet werden. Aber Sie auch Ihre Methoden etwas benennen, und dann schmücken sie mit dem [HttpGet], [HttpPost]usw. schreibt das Verb dem Verfahren abzubilden.
indot_brad
Bitte
@ DikaArtaKarunia kein Problem, froh, dass meine Antwort noch 6 Jahre später gilt: D
uggeh
31

Ich finde Attribute sauberer zu verwenden, als sie manuell über Code hinzuzufügen. Hier ist ein einfaches Beispiel.

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

}

Sie benötigen dies auch in Ihrer 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 }
);

Einige gute Links http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Hier wird das Routing besser erklärt. http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api

Kalel Wade
quelle
3
Ich musste auch config.MapHttpAttributeRoutes();meine WebApiConfig.csund GlobalConfiguration.Configuration.EnsureInitialized();am Ende meiner WebApiApplication.Application_Start()Methode hinzufügen , damit die Routenattribute funktionieren.
Ergwun
@Ergwun Dieser Kommentar hat mir sehr geholfen. Nur um es hinzuzufügen, config.MapHttpAttributeRoutes();muss vor der Routenkartierung erscheinen (zB vorher config.Routes.MappHttpRoute(....
Philip Stratford
11

Sie müssen weitere Routen in global.asax.cs wie folgt definieren:

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 }
);
Alexander Zeitler
quelle
5
Ja, das stimmt, aber es wäre schön, ein Beispiel für diese Routen zu sehen. Dies würde diese Antwort für die Community wertvoller machen. (und Sie würden eine +1 von mir bekommen :)
Aran Mulholland
Sie können ein Beispiel hier lesen - stackoverflow.com/questions/11407267/…
Tom Kerkhove
2
Eine tatsächliche Lösung wäre schöner gewesen.
So viele Goblins
6

Mit der neueren Web-API 2 ist es einfacher geworden, mehrere Get-Methoden zu haben.

Wenn die an die GETMethoden übergebenen Parameter so unterschiedlich sind, dass das Attribut-Routing-System ihre Typen unterscheiden kann, wie dies bei ints und Guids der Fall ist , können Sie den erwarteten Typ im [Route...]Attribut angeben

Zum Beispiel -

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

Weitere Informationen zu diesem Ansatz finden Sie hier http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

Eine andere Möglichkeit besteht darin, den GETMethoden unterschiedliche Routen zuzuweisen.

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

Weitere Informationen finden Sie hier - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/

Bryan
quelle
5

In ASP.NET 2.0 Core können Sie hinzufügen Routen Attribut an die Steuerung:

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

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

Ich habe versucht, Web Api 2-Attributrouting zu verwenden, um mehrere Get-Methoden zuzulassen, und ich hatte die hilfreichen Vorschläge aus früheren Antworten aufgenommen, aber im Controller hatte ich nur die "spezielle" Methode (Beispiel) dekoriert:

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

... ohne auch ein [RoutePrefix] oben auf dem Controller zu platzieren:

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

Ich habe Fehler erhalten, die besagen, dass keine Route gefunden wurde, die mit der übermittelten URI übereinstimmt. Nachdem ich sowohl die [Route] als auch die [RoutePrefix] als Dekoration für den Controller als Ganzes hatte, funktionierte es.

StackOverflowUser
quelle
3

Ich bin nicht sicher, ob Sie die Antwort gefunden haben, aber ich habe dies getan und es funktioniert

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

Jetzt in 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 }
);
Pavan Josyula
quelle
3

Haben Sie versucht, auf WebInvokeAttribute umzuschalten und die Methode auf "GET" zu setzen?

Ich glaube, ich hatte ein ähnliches Problem und wechselte zu der expliziten Aussage, welche Methode (GET / PUT / POST / DELETE) für die meisten, wenn nicht alle meiner Methoden erwartet wird.

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

Das WebGet sollte damit umgehen, aber ich habe gesehen, dass es einige Probleme mit mehreren Get viel weniger multiplen Get vom gleichen Rückgabetyp hat.

[Bearbeiten: Nichts davon ist gültig mit dem Sonnenuntergang von WCF WebAPI und der Migration zu ASP.Net WebAPI auf dem MVC-Stapel]

PMontgomery
quelle
1
Es tut mir leid, ich habe vergessen zu erwähnen, dass ich den Code in die ASP.NET-Web-API verschiebe, da die WCF-Web-API eingestellt wurde. Ich habe den Beitrag bearbeitet. Danke dir.
Paulius_l
2
**Add Route function to direct the routine what you want**
    public class SomeController : ApiController
    {
        [HttpGet()]
        [Route("GetItems")]
        public SomeValue GetItems(CustomParam parameter) { ... }

        [HttpGet()]
        [Route("GetChildItems")]
        public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
    }
JackyShen
quelle
Willkommen bei Stack Overflow! Bitte bearbeiten Sie Ihre Antwort , um eine Erklärung für Ihren Code sowie eine Beschreibung zu enthalten, wie er sich von den vierzehn anderen Antworten hier unterscheidet. Diese Frage ist fast acht Jahre alt und hat bereits eine akzeptierte und mehrere gut erläuterte Antwort. Ohne eine Erklärung zu Ihrer wird es wahrscheinlich herabgestimmt oder entfernt. Wenn Sie diese Erklärung haben, können Sie den Platz Ihrer Antwort auf diese Frage rechtfertigen.
Das_Geek
1
Persönlich (ich weiß, was SOs Empfehlungen sind) für eine so klare / grundlegende Frage hätte ich persönlich viel lieber eine reine Code- Antwort. Ich möchte nicht viele Erklärungen lesen. Ich möchte schnell hilfreiche funktionale Software erstellen . +1
MemeDeveloper
2

Die faule / eilige Alternative (Dotnet Core 2.2):

[HttpGet("method1-{item}")]
public string Method1(var item) { 
return "hello" + item;}

[HttpGet("method2-{item}")]
public string Method2(var item) { 
return "world" + item;}

Ich nenne sie:

localhost: 5000 / api / controllerername / method1-42

"hallo42"

localhost: 5000 / api / controllerername / method2-99

"world99"

Arthur Zennig
quelle
0

Keines der oben genannten Beispiele entsprach meinen persönlichen Bedürfnissen. Das Folgende ist, was ich letztendlich getan habe.

 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;             

    }

Um das oben Genannte in Ihrer Route zu verwenden, verwenden Sie:

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

Was passiert, ist die Art von Fälschungen in der Methode, sodass diese Route nur mit den Standardmethoden GET, POST, PUT und DELETE übereinstimmt. Das "wahre" dort besagt, dass wir nach einer Übereinstimmung der Elemente im Array suchen möchten. Wenn es falsch wäre, würden Sie sagen, schließen Sie diejenigen in der strYou aus. Sie können dann Routen über dieser Standardmethode verwenden, wie:

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

Oben wird im Wesentlichen nach der folgenden URL gesucht => http://www.domain.com/Account/Status/Activeoder so ähnlich.

Darüber hinaus bin ich mir nicht sicher, ob ich zu verrückt werden würde. Am Ende des Tages sollte es pro Ressource sein. Ich sehe jedoch aus verschiedenen Gründen die Notwendigkeit, freundliche URLs zuzuordnen. Ich bin mir ziemlich sicher, dass es eine Art Bereitstellung geben wird, wenn sich Web Api weiterentwickelt. Wenn es Zeit ist, werde ich eine dauerhaftere Lösung erstellen und posten.

origin1tech
quelle
Sie können new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) stattdessen verwenden.
Abatishchev
0

Ich konnte keine der oben genannten Routing-Lösungen zum Laufen bringen - ein Teil der Syntax scheint sich geändert zu haben und ich bin noch neu in MVC -, obwohl ich diesen wirklich schrecklichen (und einfachen) Hack zusammengestellt habe, der mich dazu bringen wird Beachten Sie, dass dies die Methode "public MyObject GetMyObjects (long id)" ersetzt. Wir ändern den Typ "id" in eine Zeichenfolge und den Rückgabetyp in 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;
}
BrainSlugs83
quelle
0

Wenn Sie mehrere Aktionen in derselben Datei haben, übergeben Sie dasselbe Argument, z. B. ID, an alle Aktionen. Dies liegt daran, dass die Aktion nur die ID identifizieren kann. Anstatt dem Argument einen Namen zu geben, deklarieren Sie nur die ID wie folgt.


[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 }
);
Uttam Kumar
quelle
Wie würde die URL aussehen, um jede Funktion im Browser anzuzeigen?
Si8
0

Einfache Alternative

Verwenden Sie einfach eine Abfragezeichenfolge.

Routing

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

Regler

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

    public SomeViewModel GetById(int objectId)
    {
    }
}

Anfragen

GET /Test
GET /Test?objectId=1

Hinweis

Beachten Sie, dass der Parameter für die Abfragezeichenfolge nicht "id" sein sollte oder was auch immer der Parameter in der konfigurierten Route ist.

Seth Blumen
quelle
-1

Ändern Sie die WebApiConfig und fügen Sie am Ende eine weitere Routes.MapHttpRoute wie folgt hinzu:

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

Erstellen Sie dann einen Controller wie folgt:

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

So habe ich es gelöst. Ich hoffe es wird jemandem helfen.

Eduardo Mercado
quelle