web-dev-qa-db-ja.com

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

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メソッドだけでなく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

私はExecuteAsyncApiControllerメソッドをオーバーライドすることによってこの問題に取り組もうとしていますが、これまでのところ運はありません。この問題について何かアドバイスはありますか?

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

146
paulius_l

これが私が見つけた最良の方法で、余分な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アクションが 'Get'で始まっていない場合は、HttpGet属性をメソッドに追加することをお勧めします。

239
sky-dev

これから行く:

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サービスアプリケーションで試してみましたが、それは魅力のように機能します:)

54
uggeh

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

[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

29
Kalel Wade

次のように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 }
);
11

新しい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
Bryan

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

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

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
Pavan Josyula

WebInvokeAttributeに切り替えて、Methodを "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への移行では有効ではない]

3
PMontgomery

私は複数のGetメソッドを可能にするためにWeb Api 2属性ルーティングを使用しようとしていました、そして私は前の答えからの有用な提案を取り入れました、しかし私は「特別な」メソッドを装飾しました(例):

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

...また、Controllerの一番上に[RoutePrefix]を配置することなく:

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

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

2

上記のルーティングソリューションをうまく機能させることはできませんでした - 構文の一部が変更されたようで、まだMVCにまだ慣れていません。今のところ - これは、 "public MyObject GetMyObjects(long id)"メソッドに代わるものです - "id"の型を文字列に変更し、戻り型を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

同じファイル内に複数のActionがある場合は、同じ引数を渡します。すべてのアクションのID。これはactionが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 }
);
0
Uttam Kumar

上記の例のどれも私の個人的な必要性のために働きませんでした。以下は私がやったことです。

 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メソッドにのみ一致するように、メソッド内での偽造が制限されます。そこに「真」とは、配列内の項目の一致を確認したいということです。それが偽であったならば、あなたはstrのそれらを除外すると言っているでしょうあなたはそれからこのようなこのデフォルト方法の上のルートを使うことができます:

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

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

上記以外にも、私は夢中になりすぎるかどうかわかりません。一日の終わりにそれはリソースごとにあるはずです。しかし、私はさまざまな理由でフレンドリーなURLをマッピングする必要があると思います。 Web Apiが進化していくにつれてある種の規定があると確信しています。時間があれば私はより恒久的な解決策を構築して投稿します。

0
origin1tech

単純な代替

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

ルーティング

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"や、設定されたルート内のパラメータには関係ありません。

0
Seth Flowers