Pengontrol tunggal dengan beberapa metode GET di ASP.NET Web API

167

Di Web API saya memiliki kelas struktur yang serupa:

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

Karena kami dapat memetakan masing-masing metode, sangat mudah untuk mendapatkan permintaan yang tepat di tempat yang tepat. Untuk kelas serupa yang hanya memiliki satu GETmetode tetapi juga memiliki Objectparameter, saya berhasil menggunakan IActionValueBinder. Namun, dalam kasus yang dijelaskan di atas saya mendapatkan kesalahan berikut:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

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

Saya mencoba untuk mendekati masalah ini dengan mengganti ExecuteAsyncmetode ApiControllertetapi tidak berhasil sejauh ini. Adakah saran untuk masalah ini?

Sunting: Saya lupa menyebutkan bahwa sekarang saya mencoba untuk memindahkan kode ini di ASP.NET Web API yang memiliki pendekatan berbeda untuk perutean. Pertanyaannya adalah, bagaimana cara membuat kode bekerja di ASP.NET Web API?

paulius_l
sumber
1
Apakah Anda masih mendapatkan {parent} sebagai RouteParameter.Optional?
Antony Scott
Ya saya lakukan. Mungkin saya menggunakan IActionValueBinder dengan cara yang salah karena untuk tipe seperti int id (seperti dalam demo) tidak berfungsi dengan baik.
paulius_l
Maaf, saya seharusnya lebih jelas. Saya akan berpikir bahwa menjadikannya opsional berarti cocok dengan rute Item dan juga rute sub-item, yang akan menjelaskan pesan kesalahan yang Anda lihat.
Antony Scott
Kami saat ini sedang melakukan diskusi, jika pendekatan di bawah ini (dengan beberapa rute) bertentangan dengan aturan REST yang tepat? Menurut saya ini tidak masalah. Rekan kerja saya menganggap itu tidak baik. Ada komentar tentang ini?
Remy
Saya umumnya menentangnya ketika mulai membaca tentang REST. Saya masih tidak yakin apakah itu pendekatan yang tepat tapi kadang-kadang lebih mudah atau ramah pengguna, jadi sedikit membengkokkan aturan mungkin tidak terlalu buruk. Selama itu berhasil menyelesaikan masalah tertentu. 6 bulan telah berlalu sejak saya memposting pertanyaan ini dan kami tidak menyesal menggunakan pendekatan ini sejak saat itu.
paulius_l

Jawaban:

249

Ini adalah cara terbaik yang saya temukan untuk mendukung metode GET tambahan dan mendukung metode REST normal juga. Tambahkan rute berikut ke WebApiConfig Anda:

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

Saya memverifikasi solusi ini dengan kelas tes di bawah ini. Saya berhasil mencapai setiap metode di controller saya di bawah ini:

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

Saya memverifikasi bahwa itu mendukung permintaan berikut:

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

Perhatikan bahwa jika tindakan GET tambahan Anda tidak dimulai dengan 'Dapatkan' Anda mungkin ingin menambahkan atribut HttpGet ke metode ini.

langit-dev
sumber
4
Ini adalah jawaban yang bagus dan banyak membantu saya dengan pertanyaan terkait lainnya. Terima kasih!!
Alfero Chingono
4
Mencoba ini - tampaknya tidak berfungsi. Rute semua dipetakan secara acak ke metode GetBlah (id panjang). :(
BrainSlugs83
1
@ BrainSlugs83: Tergantung pesanan. Dan Anda akan ingin menambahkan (ke metode "withId"), aconstraints: new{id=@"\d+"}
Eric Falsken
4
bagaimana dengan menambahkan satu metode lagi - Dapatkan (int id, nama string)? ... gagal
Anil Purswani
1
Saya harus menambahkan rute tambahan seperti ini routes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});untuk Putmetode saya jika tidak memberi saya 404.
Syed Ali Taqi
57

Pergi dari ini:

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

Untuk ini:

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

Karenanya, Anda sekarang dapat menentukan tindakan (metode) mana yang ingin Anda kirimi permintaan HTTP Anda.

posting ke "http: // localhost: 8383 / api / Command / PostCreateUser" memanggil:

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

dan memposting ke "http: // localhost: 8383 / api / Command / PostMakeBooking" memanggil:

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

Saya mencoba ini di aplikasi layanan WEB API yang di-host sendiri dan berfungsi seperti pesona :)

uggeh
sumber
8
Terima kasih atas jawabannya. Saya ingin menambahkan bahwa jika Anda memulai nama metode dengan Get, Post, dll., Permintaan Anda akan dipetakan ke metode-metode tersebut berdasarkan kata kerja HTTP yang digunakan. Tapi Anda juga dapat nama apapun metode Anda, dan kemudian menghiasi mereka dengan [HttpGet], [HttpPost], dll atribut untuk memetakan kata kerja ke metode.
indot_brad
silakan lihat pertanyaan
Moeez
@DikaArtaKarunia tidak masalah, senang bahwa jawaban saya masih berlaku 6 tahun kemudian: D
uggeh
31

Saya menemukan atribut lebih bersih untuk digunakan daripada menambahkannya secara manual melalui kode. Ini adalah contoh sederhana.

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

}

Anda juga memerlukan ini di konfigurasi web Anda

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

Beberapa Tautan Baik http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Yang ini menjelaskan perutean dengan lebih baik. http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api

Kalel Wade
sumber
3
Saya perlu juga menambahkan config.MapHttpAttributeRoutes();ke saya WebApiConfig.cs, dan GlobalConfiguration.Configuration.EnsureInitialized();pada akhir WebApiApplication.Application_Start()metode saya untuk mendapatkan atribut rute untuk bekerja.
Ergwun
@ Ergwun Komentar ini banyak membantu saya. Hanya untuk menambahkannya, config.MapHttpAttributeRoutes();perlu muncul sebelum pemetaan rute (mis config.Routes.MappHttpRoute(.... Sebelumnya .
Philip Stratford
11

Anda perlu menentukan rute lebih lanjut di global.asax.cs seperti ini:

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
sumber
5
Ya itu benar tetapi akan menyenangkan untuk benar-benar melihat contoh dari rute tersebut. Itu akan membuat jawaban ini lebih berharga bagi masyarakat. (dan Anda akan mendapat +1 dari saya :)
Aran Mulholland
Anda dapat membaca contoh di sini - stackoverflow.com/questions/11407267/…
Tom Kerkhove
2
Solusi aktual akan lebih baik.
Begitu Banyak Goblin
6

Dengan Web Api 2 yang lebih baru menjadi lebih mudah untuk memiliki beberapa metode get.

Jika parameter yang diteruskan ke GETmetode cukup berbeda untuk sistem perutean atribut untuk membedakan tipenya seperti halnya dengan ints dan Guids, Anda dapat menentukan jenis yang diharapkan dalam [Route...]atribut

Sebagai contoh -

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

Untuk detail lebih lanjut tentang pendekatan ini, lihat di sini http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

Pilihan lain adalah memberikan GETmetode rute yang berbeda.

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

Lihat di sini untuk perincian lebih lanjut - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/

Bryan
sumber
5

Di ASP.NET Core 2.0 Anda bisa menambahkan atribut Route ke controller:

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

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

Saya mencoba menggunakan perutean atribut Web Api 2 untuk memungkinkan beberapa metode Get, dan saya telah memasukkan saran yang bermanfaat dari jawaban sebelumnya, tetapi di Controller saya hanya menghiasi metode "khusus" (contoh):

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

... tanpa juga menempatkan [RoutePrefix] di bagian atas Controller:

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

Saya mendapatkan kesalahan yang menyatakan bahwa tidak ada rute yang ditemukan cocok dengan URI yang dikirimkan. Setelah saya memiliki [Rute] dekorasi metode serta [RoutePrefix] mendekorasi Controller secara keseluruhan, itu berhasil.

StackOverflowUser
sumber
3

Saya tidak yakin apakah Anda telah menemukan jawabannya, tetapi saya melakukan ini dan itu berhasil

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

Sekarang di 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
sumber
3

Sudahkah Anda mencoba beralih ke WebInvokeAttribute dan mengatur Metode ke "GET"?

Saya percaya saya memiliki masalah yang sama dan beralih ke secara eksplisit mengatakan Metode mana (GET / PUT / POST / DELETE) yang diharapkan pada sebagian besar, jika tidak semua, metode saya.

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 harus menanganinya tetapi saya pernah melihatnya memiliki beberapa masalah dengan beberapa Dapatkan jauh lebih sedikit Dapatkan dari jenis pengembalian yang sama.

[Sunting: semua ini tidak berlaku dengan matahari terbenam WCF WebAPI dan migrasi ke ASP.Net WebAPI pada tumpukan MVC]

PMontgomery
sumber
1
Maaf, saya lupa menyebutkan bahwa saya memindahkan kode ke ASP.NET Web API karena WCF Web API dihentikan. Saya mengedit posting. Terima kasih.
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
sumber
Selamat Datang di Stack Overflow! Harap edit jawaban Anda untuk memasukkan penjelasan untuk kode Anda, serta deskripsi tentang perbedaannya dari empat belas jawaban lain di sini. Pertanyaan ini sudah hampir delapan tahun , dan sudah memiliki jawaban yang diterima dan beberapa penjelasan yang baik. Tanpa penjelasan tentang Anda , itu kemungkinan akan diturunkan atau dihapus. Memiliki penjelasan itu akan membantu membenarkan tempat jawaban Anda pada pertanyaan ini.
Das_Geek
1
Secara pribadi (saya tahu apa rekomendasi SO) untuk pertanyaan ini jelas / dasar saya pribadi lebih suka memiliki jawaban kode murni . Saya tidak ingin membaca banyak penjelasan. Saya ingin membuat perangkat lunak fungsional yang cepat membantu . +1
MemeDeveloper
2

Alternatif malas / tergesa-gesa (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;}

Memanggil mereka:

localhost: 5000 / api / controllername / method1-42

"hello42"

localhost: 5000 / api / controllername / method2-99

"world99"

Arthur Zennig
sumber
0

Tidak satu pun dari contoh di atas yang berfungsi untuk kebutuhan pribadi saya. Di bawah ini adalah apa yang akhirnya saya lakukan.

 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;             

    }

Untuk menggunakan hal di atas dalam penggunaan rute Anda:

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

Apa yang terjadi adalah kendala jenis palsu dalam metode sehingga rute ini hanya akan cocok dengan metode GET, POST, PUT dan DELETE standar. "True" di sana mengatakan kami ingin memeriksa kecocokan item dalam array. Jika itu salah Anda akan mengatakan mengecualikan orang-orang di strYou kemudian dapat menggunakan rute di atas metode default ini seperti:

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

Di atas pada dasarnya mencari URL berikut => http://www.domain.com/Account/Status/Activeatau sesuatu seperti itu.

Di luar di atas saya tidak yakin saya akan terlalu gila. Pada akhirnya itu harus per sumber daya. Tapi saya melihat perlunya memetakan url ramah karena berbagai alasan. Saya merasa cukup yakin karena Web Api berkembang akan ada semacam ketentuan. Jika waktu saya akan membangun solusi dan posting yang lebih permanen.

origin1tech
sumber
Anda bisa menggunakannya new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) .
abatishchev
0

Tidak dapat membuat salah satu solusi perutean di atas berfungsi - beberapa sintaks tampaknya telah berubah dan saya masih baru di MVC - dalam keadaan darurat meskipun saya mengumpulkan peretasan yang benar-benar mengerikan (dan sederhana) ini yang akan membuat saya oleh untuk sekarang - perhatikan, ini menggantikan metode "MyObject GetMyObjects (long id)" publik - kami mengubah tipe "id" menjadi string, dan mengubah tipe kembali ke objek.

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

Jika Anda memiliki beberapa Tindakan dalam file yang sama, maka berikan argumen yang sama, misalnya Id untuk semua Tindakan. Ini karena tindakan hanya dapat mengidentifikasi Id, Jadi alih-alih memberikan nama apa pun ke argumen, hanya menyatakan Id seperti ini.


[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
sumber
Bagaimana tampilan Url untuk melihat setiap fungsi di browser?
Si8
0

Alternatif Sederhana

Cukup gunakan string kueri.

Rute

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

Pengendali

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

    public SomeViewModel GetById(int objectId)
    {
    }
}

Permintaan

GET /Test
GET /Test?objectId=1

Catatan

Perlu diingat bahwa param string kueri tidak boleh "id" atau apa pun parameter di rute yang dikonfigurasi.

Seth Flowers
sumber
-1

Ubah WebApiConfig dan tambahkan di akhir Routes.MapHttpRoute seperti ini:

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

Kemudian buat pengontrol seperti ini:

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

Ini adalah bagaimana saya menyelesaikannya. Saya harap ini akan membantu seseorang.

Eduardo Mercado
sumber