ASP.NET Web APIの複数のGETメソッドを持つ単一のコントローラー


167

Web APIには、同様の構造のクラスがありました。

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

個々のメソッドをマッピングできるため、適切な場所で適切なリクエストを取得することは非常に簡単でした。GETメソッドが1つだけで、Objectパラメーターも持つ同様のクラスの場合、を正常に使用しましたIActionValueBinder。ただし、上記の場合、次のエラーが発生します。

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

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

私はこのExecuteAsync方法をオーバーライドすることによってこの問題に取り組みますが、ApiController今のところ運がありません。この問題について何かアドバイスはありますか?

編集:ルーティングへの異なるアプローチを持つASP.NET Web APIにこのコードを移動しようとしていることを言及するのを忘れていました。問題は、ASP.NET Web APIでコードをどのように機能させるかです。


1
まだ{Parameter}をRouteParameter.Optionalとして取得していますか?
アントニースコット

はい、そうしました。IActionValueBinderを間違った方法で使用している可能性があります。これは、(デモのように)int idなどのタイプでは正常に機能するためです。
paulius_l 2012

すみません、もっとはっきりしていたはずです。これをオプションとして設定すると、アイテムのルートやサブアイテムのルートと一致することになるため、表示されているエラーメッセージが説明されると思いました。
アントニースコット

以下のアプローチ(複数のルートを使用)が適切なRESTルールに違反している場合、私たちは現在議論しています。私の意見ではこれで結構です。同僚はそれを良くないと思っています。これについて何かコメントはありますか?
レミー

RESTについて読み始めたとき、私は一般的にそれに反対しました。それが適切なアプローチであるかどうかはまだわかりませんが、時にはそれがより便利で使いやすいので、ルールを少し曲げることはそれほど悪くないかもしれません。特定の問題を解決するために機能する限り。私がこの質問を投稿してから6か月が経過しました。それ以来、このアプローチを使用することに後悔はありません。
paulius_l 2012

回答:


249

これは、追加のGETメソッドをサポートし、通常のRESTメソッドもサポートするために私が見つけた最良の方法です。以下のルートを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)});

以下のテストクラスでこのソリューションを検証しました。以下のコントローラーで各メソッドを正常にヒットできました:

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

次のリクエストをサポートしていることを確認しました。

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

あなたの余分なGETアクションがで始まらない場合は、メソッドにHTTPGET属性を追加することもGET「」は。


4
これは素晴らしい答えであり、別の関連する質問で私を大いに助けました。ありがとう!!
Alfero Chingono 2012年

4
これを試しました-動作していないようです。ルートはすべてGetBlah(long id)メソッドにランダムにマッピングされます。:(
BrainSlugs83 2013年

1
@ BrainSlugs83:注文によって異なります。そして、あなたは、(「withId」メソッドに)追加したいでしょうconstraints: new{id=@"\d+"}
エリックFalsken

4
Get(int id、string name)メソッドをもう1つ追加してみませんか?...失敗する
Anil Purswani 2013年

1
私はこのような余分なルートを追加しなければならなかったroutes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});私のためにPutそれ以外の場合は私に404与えていた方法
サイード・アリのTaqI

57

これから行く:

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

これに:

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

したがって、HTTPリクエストの送信先のアクション(メソッド)を指定できるようになりました。

「http:// localhost:8383 / api / Command / PostCreateUser」に投稿すると、次のようになります。

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

「http:// localhost:8383 / api / Command / PostMakeBooking」に投稿すると、次のようになります。

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

私はこれをセルフホスト型のWEB APIサービスアプリケーションで試してみましたが、魅力的に機能します:)


8
役立つ回答をありがとう。メソッド名をGet、Postなどで始めると、リクエストは使用されるHTTP動詞に基づいてそれらのメソッドにマッピングされることを付け加えておきます。ただし、メソッドに任意の名前を付けて、、などの属性[HttpGet]で装飾し[HttpPost]、動詞をメソッドにマッピングすることもできます。
indot_brad 2013年

親切に私の質問を
Moeez

@DikaArtaKarunia問題ありません。私の回答が6年後も適用できることをうれしく思います:D
uggeh

31

コードを使用して手動で追加するよりも、属性を使用する方がわかりやすいと思います。これは簡単な例です。

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

}

これは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 }
);

いくつかの良いリンク http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api これは、ルーティングをよりよく説明しています。 http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api


3
ルート属性を機能させるために、メソッドの最後にも追加config.MapHttpAttributeRoutes();する必要がWebApiConfig.csありGlobalConfiguration.Configuration.EnsureInitialized();ましたWebApiApplication.Application_Start()
Ergwun 2017

@Ergwunこのコメントは私に大いに役立ちました。それに追加するだけで、config.MapHttpAttributeRoutes();例えば前に(ルートマッピングの前に出現する必要がありますconfig.Routes.MappHttpRoute(...
フィリップ・ストラットフォード

11

次のように、global.asax.csでさらにルートを定義する必要があります。

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

5
はい、そうですが、実際にこれらのルートの例を見るとよいでしょう。それはこの答えをコミュニティにとってより価値のあるものにします。(そして私から+1がもらえます:)
アラン・マルホランド

あなたはここに例を読むことができます- stackoverflow.com/questions/11407267/...
トムKerkhove

2
実際の解決策はより優れていたでしょう。
多数のゴブリン

6

新しいWeb Api 2では、複数のgetメソッドを持つことがより簡単になりました。

GETメソッドに渡されるパラメーターが、intsおよびGuidsの場合のように属性ルーティングシステムがそれらのタイプを区別できるほど十分に異なる場合は、[Route...]属性に期待されるタイプを指定できます。

例えば ​​-

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

このアプローチの詳細については、こちらを参照してくださいhttp://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

別のオプションは、GETメソッドに異なるルートを与えることです。

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

詳細については、こちらをご覧ください-http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/


5

ASP.NET Core 2.0では、Route属性をコントローラーに追加できます。

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

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

4

複数のGetメソッドを可能にするためにWeb Api 2属性ルーティングを使用しようとしていましたが、以前の回答からの有用な提案を組み込んでいましたが、コントローラーでは "特別な"メソッド(例)のみを装飾していました:

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

...コントローラーの上部に[RoutePrefix]も配置しない場合:

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

送信されたURIに一致するルートが見つからなかったというエラーが発生しました。[Route]でメソッドを装飾したり、[RoutePrefix]でコントローラー全体を装飾したりすると、うまくいきました。


3

あなたが答えを見つけたかどうかはわかりませんが、私はこれをしましたそしてそれはうまくいきます

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

今度は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

WebInvokeAttributeに切り替えて、メソッドを「GET」に設定してみましたか?

私は同様の問題があり、すべての方法ではないにしても、ほとんどのメソッドで予想されるメソッド(GET / PUT / POST / DELETE)を明示的に伝えることに切り替えたと思います。

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 それ処理する必要がありますが、同じ戻り値の型の複数のGetよりもはるかに少ない複数のGetでいくつかの問題があることを確認しました。

[編集:WCF WebAPIの廃止およびMVCスタック上のASP.Net WebAPIへの移行では、これらのいずれも有効ではありません]


1
申し訳ありませんが、WCF Web APIが廃止されたため、コードをASP.NET Web APIに移動していることを忘れていました。投稿を編集しました。ありがとうございました。
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) { ... }
    }

Stack Overflowへようこそ!してくださいあなたの答えを編集し、あなたのコードの説明と同様に、それはここ14の他の答えは異なっているかの説明を含めること。この質問はほぼ8年前のものであり、すでに受け入れられ、いくつかのよく説明された回答があります。あなたの説明がなければ、それはおそらく反対投票または削除されます。その説明があることは、この質問に対するあなたの答えの立場を正当化するのに役立ちます。
Das_Geek

1
個人的に(私はSOの推奨事項を知っています)、この明確で基本的な質問に対しては、個人には純粋なコードの回答を求めます。たくさんの説明を読みたくない役に立つ機能的なソフトウェアを速くしたい。+1
MemeDeveloper

2

レイジー/ハリーの代替(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;}

それらを呼び出す:

localhost:5000 / api / controllername / method1-42

「hello42」

localhost:5000 / api / controllername / method2-99

「world99」


0

上記の例はどれも私の個人的なニーズに対応できませんでした。以下は私がやったことです。

 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;             

    }

ルートで上記を使用するには:

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

何が起こるかは、このルートがデフォルトのGET、POST、PUT、およびDELETEメソッドにのみ一致するように、メソッドの制約の種類の偽物です。そこにある「真」は、配列内の項目の一致をチェックすることを示しています。falseの場合は、str内のルートを除外することになります。次に、次のように、このデフォルトのメソッドより上のルートを使用できます。

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

上記では、本質的に次のURL => http://www.domain.com/Account/Status/Activeまたはそのようなものを探しています。

上記を超えて、私が狂ってしまうかどうかはわかりません。結局のところ、それはリソースごとのはずです。しかし、さまざまな理由から、わかりやすいURLをマップする必要があると思います。Web APIが進化するので、ある種の規定があると確信しています。時間があれば、より永続的なソリューションを構築して投稿します。


new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) 代わりに使用できます。
abatishchev 2014年

0

上記のルーティングソリューションを機能させることができませんでした-構文の一部が変更されたようですが、私はまだMVCを初めて使用しています-本当にひどい(そして単純な)ハックを組み合わせて、今のところ-これは "public MyObject GetMyObjects(long id)"メソッドに代わるものです-"id"のタイプを文字列に変更し、戻り値のタイプをオブジェクトに変更します。

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

同じファイル内に複数のアクションがある場合、すべてのアクションに同じ引数、たとえばIdを渡します。これは、アクションはIdのみを識別できるためです。引数に名前を付けるのではなく、IDをこのように宣言するだけです。


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

URLはブラウザで各関数をどのように表示しますか?
Si8 2017年

0

シンプルな代替

クエリ文字列を使用するだけです。

ルーティング

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

コントローラ

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

    public SomeViewModel GetById(int objectId)
    {
    }
}

リクエスト

GET /Test
GET /Test?objectId=1

注意

クエリ文字列paramは、 "id"や、構成されたルート内のパラメータにしないでください。


-1

WebApiConfigを変更して、次のように最後に別のRoutes.MapHttpRouteを追加します。

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

次に、次のようなコントローラーを作成します。

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

これは私がそれを解決した方法です。私はそれが誰かを助けることを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.