Bộ điều khiển đơn với nhiều phương thức GET trong ASP.NET Web API


167

Trong API Web tôi có một lớp cấu trúc tương tự:

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

Vì chúng ta có thể ánh xạ các phương thức riêng lẻ, rất đơn giản để có được yêu cầu đúng ở đúng nơi. Đối với lớp tương tự chỉ có một GETphương thức duy nhất nhưng cũng có một Objecttham số, tôi đã sử dụng thành công IActionValueBinder. Tuy nhiên, trong trường hợp được mô tả ở trên, tôi nhận được lỗi sau:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

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

Tôi đang cố gắng tiếp cận vấn đề này bằng cách ghi đè ExecuteAsyncphương pháp ApiControllernhưng không có may mắn cho đến nay. Có lời khuyên nào về vấn đề này?

Chỉnh sửa: Tôi quên đề cập rằng bây giờ tôi đang cố gắng di chuyển mã này trên API Web ASP.NET có cách tiếp cận khác để định tuyến. Câu hỏi là, làm cách nào để làm cho mã hoạt động trên ASP.NET Web API?


1
Bạn vẫn có {cha} là RouteParameter.Optional?
Antony Scott

Vâng, tôi đã làm. Có thể tôi đang sử dụng IActionValueBinder sai cách vì đối với các loại như int id (như trong bản demo) thì nó hoạt động tốt.
paulius_l

Xin lỗi, tôi nên đã rõ ràng hơn. Tôi đã nghĩ rằng việc có nó là tùy chọn sẽ có nghĩa là nó phù hợp với tuyến Mục cũng như tuyến phụ, điều này sẽ giải thích thông báo lỗi bạn đang thấy.
Antony Scott

Chúng tôi hiện đang có sự xáo trộn, nếu các cách tiếp cận dưới đây (với nhiều tuyến đường) có chống lại các quy tắc REST phù hợp không? Theo tôi điều này là tốt. Đồng nghiệp của tôi nghĩ rằng nó không tốt đẹp. Bất kỳ ý kiến ​​về điều này?
Rém

Tôi thường chống lại nó khi bắt đầu đọc về REST. Tôi vẫn không chắc liệu đó có phải là một cách tiếp cận đúng đắn hay không, nhưng đôi khi nó thuận tiện hơn hoặc thân thiện với người dùng hơn, vì vậy hơi uốn cong các quy tắc có thể không quá tệ. Miễn là nó hoạt động để giải quyết một vấn đề cụ thể. Đã 6 tháng trôi qua kể từ khi tôi đăng câu hỏi này và chúng tôi không hề hối hận vì đã sử dụng phương pháp này kể từ đó.
paulius_l

Câu trả lời:


249

Đây là cách tốt nhất mà tôi đã tìm thấy để hỗ trợ các phương thức GET bổ sung và cũng hỗ trợ các phương thức REST bình thường. Thêm các tuyến đường sau vào WebApiConfig của bạn:

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

Tôi đã xác minh giải pháp này với lớp kiểm tra dưới đây. Tôi đã có thể nhấn thành công từng phương thức trong bộ điều khiển của mình bên dưới:

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

Tôi xác nhận rằng nó hỗ trợ các yêu cầu sau:

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

Lưu ý rằng nếu các hành động GET thêm của bạn không bắt đầu bằng 'Nhận', bạn có thể muốn thêm thuộc tính HttpGet vào phương thức.


4
Đây là một câu trả lời tuyệt vời và nó đã giúp tôi rất nhiều với một câu hỏi liên quan khác. Cảm ơn!!
Alfero Chingono

4
Đã thử điều này - không xuất hiện để làm việc. Tất cả các tuyến được ánh xạ ngẫu nhiên theo phương thức GetB nhớ (id dài). :(
BrainSlugs83

1
@ BrainSlugs83: Nó phụ thuộc vào thứ tự. Và bạn sẽ muốn thêm (vào các phương thức "withId"), aconstraints: new{id=@"\d+"}
Eric Falsken

4
làm thế nào về việc thêm một phương thức nữa - Nhận (int id, tên chuỗi)? ... nó thất bại
Anil Purswani

1
Tôi đã phải thêm một tuyến đường như thế này routes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});cho Putphương pháp của mình nếu không nó sẽ cho tôi 404.
Syed Ali Taqi

57

Đi từ đây:

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

Về điều này:

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

Do đó, bây giờ bạn có thể chỉ định hành động (phương thức) nào bạn muốn gửi yêu cầu HTTP của mình tới.

đăng lên "http: // localhost: 8383 / api / Command / PostCreateUser" gọi:

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

và đăng lên "http: // localhost: 8383 / api / Command / PostMakeBooking" :

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

Tôi đã thử điều này trong một ứng dụng dịch vụ API WEB tự lưu trữ và nó hoạt động như một cơ duyên :)


8
Cảm ơn câu trả lời hữu ích. Tôi muốn thêm rằng nếu bạn bắt đầu tên phương thức của mình bằng Get, Post, v.v., các yêu cầu của bạn sẽ ánh xạ tới các phương thức đó dựa trên động từ HTTP được sử dụng. Nhưng bạn cũng có thể đặt tên cho phương pháp bất cứ điều gì của bạn, và sau đó trang trí chúng với [HttpGet], [HttpPost]vv thuộc tính để lập bản đồ động từ để phương pháp này.
indot_brad

vui lòng xem câu hỏi
Moeez

@DikaArtaKarunia không có vấn đề gì, rất vui vì câu trả lời của tôi vẫn được áp dụng 6 năm sau: D
uggeh

31

Tôi thấy các thuộc tính sẽ sạch hơn để sử dụng hơn là thêm chúng thủ công thông qua mã. Đây là một ví dụ đơn giản.

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

}

Bạn cũng cần điều này trong webapiconfig của bạn

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

Một số liên kết tốt http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Điều này giải thích việc định tuyến tốt hơn. http://www.asp.net/web-api/overview/web-api-routing-and-ilities/routing-in-aspnet-web-api


3
Tôi cũng cần phải thêm config.MapHttpAttributeRoutes();vào WebApiConfig.cs, và GlobalConfiguration.Configuration.EnsureInitialized();vào cuối WebApiApplication.Application_Start()phương thức của tôi để làm cho các thuộc tính tuyến đường hoạt động.
Ergwun

@Ergwun Nhận xét này đã giúp tôi rất nhiều. Chỉ cần thêm vào nó, config.MapHttpAttributeRoutes();cần phải xuất hiện trước khi lập bản đồ tuyến đường (ví dụ trước đó config.Routes.MappHttpRoute(....
Philip Stratford

11

Bạn cần xác định các tuyến tiếp theo trong global.asax.cs như thế này:

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
Đúng, nhưng thật tuyệt khi thấy một ví dụ về các tuyến đường đó. Nó sẽ làm cho câu trả lời này có giá trị hơn cho cộng đồng. (và bạn sẽ nhận được +1 từ tôi :)
Aran Mulholland

Bạn có thể đọc một ví dụ ở đây - stackoverflow.com/questions/11407267/ từ
Tom Kerkhove

2
Một giải pháp thực tế sẽ tốt hơn.
Rất nhiều yêu tinh

6

Với Web Api 2 mới hơn, việc có nhiều phương thức get trở nên dễ dàng hơn.

Nếu tham số được truyền cho các GETphương thức đủ khác nhau để hệ thống định tuyến thuộc tính phân biệt các loại của chúng như trường hợp với ints và Guids, bạn có thể chỉ định loại dự kiến ​​trong [Route...]thuộc tính

Ví dụ -

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

Để biết thêm chi tiết về phương pháp này, xem tại đây http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controll-with-multipl-get-methods-part-2/

Một lựa chọn khác là cung cấp cho các GETphương pháp các tuyến đường khác nhau.

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

Xem tại đây để biết thêm chi tiết - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controll-with-multipl-get-methods/


5

Trong ASP.NET Core 2.0, bạn có thể thêm thuộc tính Route vào bộ điều khiển:

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

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

4

Tôi đã cố gắng sử dụng định tuyến thuộc tính Web Api 2 để cho phép nhiều phương thức Get và tôi đã kết hợp các đề xuất hữu ích từ các câu trả lời trước đó, nhưng trong Trình điều khiển tôi chỉ trang trí phương thức "đặc biệt" (ví dụ):

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

... mà cũng không đặt [RoutePrefix] ở đầu Bộ điều khiển:

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

Tôi đã nhận được thông báo lỗi rằng không tìm thấy Tuyến nào khớp với URI đã gửi. Khi tôi đã có cả [Tuyến đường] trang trí phương pháp cũng như [RoutePrefix] trang trí toàn bộ Bộ điều khiển, nó đã hoạt động.


3

Tôi không chắc nếu bạn đã tìm thấy câu trả lời, nhưng tôi đã làm điều này và nó hoạt động

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

Bây giờ trong 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

Bạn đã thử chuyển sang WebInvokeAttribution và đặt Phương thức thành "GET" chưa?

Tôi tin rằng tôi đã gặp vấn đề tương tự và chuyển sang nói rõ ràng Phương thức nào (GET / PUT / POST / DELETE) được mong đợi trên hầu hết, nếu không phải tất cả, phương thức của tôi.

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 sẽ xử lý nó nhưng tôi đã thấy nó có một số vấn đề với nhiều Nhận ít hơn nhiều Nhận cùng loại.

[Chỉnh sửa: không có gì trong số này hợp lệ với hoàng hôn của WCF WebAPI và việc di chuyển sang ASP.Net WebAPI trên ngăn xếp MVC]


1
Tôi xin lỗi, tôi đã quên đề cập rằng tôi đang chuyển mã sang API Web ASP.NET kể từ khi API Web WCF bị ngưng. Tôi đã chỉnh sửa bài viết. Cảm ơn bạn.
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) { ... }
    }

Chào mừng bạn đến với Stack Overflow! Vui lòng chỉnh sửa câu trả lời của bạn để bao gồm một lời giải thích cho mã của bạn, cũng như một mô tả về cách nó khác với mười bốn câu trả lời khác ở đây. Câu hỏi này đã gần tám tuổi và đã có một câu trả lời được chấp nhận và giải thích rõ ràng. Nếu không có lời giải thích về bạn , nó có thể sẽ bị hạ cấp hoặc xóa. Có lời giải thích đó sẽ giúp chứng minh vị trí câu trả lời của bạn cho câu hỏi này.
Das_Geek

1
Cá nhân (tôi biết các khuyến nghị của SO là gì) cho một câu hỏi rõ ràng / cơ bản này, cá nhân tôi muốn có một câu trả lời mã thuần túy . Tôi không muốn đọc nhiều lời giải thích Tôi muốn làm cho phần mềm chức năng hữu ích nhanh chóng . +1
Nhà phát triển Meme

2

Lựa chọn thay thế lười biếng / vội vàng (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;}

Gọi họ:

localhost: 5000 / api / tên người dùng / phương thức1-42

"xin chào42"

localhost: 5000 / api / tên người dùng / phương thức2-99

"thế giới99"


0

Không có ví dụ trên làm việc cho nhu cầu cá nhân của tôi. Dưới đây là những gì tôi đã làm.

 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;             

    }

Để sử dụng ở trên trong tuyến đường của bạn sử dụng:

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

Điều xảy ra là loại ràng buộc trong phương thức sao cho tuyến này sẽ chỉ khớp với các phương thức GET, POST, PUT và DELETE mặc định. "Đúng" ở đó nói rằng chúng tôi muốn kiểm tra sự trùng khớp của các mục trong mảng. Nếu đó là sai, bạn sẽ nói loại trừ những người trong strYou. Sau đó, bạn có thể sử dụng các tuyến đường trên phương thức mặc định này như:

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

Ở trên, về cơ bản, nó đang tìm kiếm URL sau => http://www.domain.com/Account/Status/Activehoặc một cái gì đó tương tự.

Ngoài những điều trên tôi không chắc mình sẽ trở nên quá điên rồ. Vào cuối ngày, nó phải là trên mỗi tài nguyên. Nhưng tôi thấy cần phải lập bản đồ các url thân thiện vì nhiều lý do. Tôi cảm thấy khá chắc chắn khi Web Api phát triển sẽ có một số loại cung cấp. Nếu có thời gian tôi sẽ xây dựng một giải pháp lâu dài hơn và đăng bài.


Bạn có thể sử dụng new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) thay thế.
abatishchev

0

Không thể làm cho bất kỳ giải pháp định tuyến nào ở trên hoạt động - một số cú pháp dường như đã thay đổi và tôi vẫn chưa quen với MVC - trong một trường hợp khó khăn mặc dù tôi đã kết hợp bản hack thực sự khủng khiếp (và đơn giản) này sẽ giúp tôi bây giờ - lưu ý, điều này thay thế phương thức "công khai MyObject GetMyObjects (id dài)" - chúng tôi thay đổi loại "id" thành một chuỗi và thay đổi kiểu trả về thành đối tượng.

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

Nếu bạn có nhiều Hành động trong cùng một tệp thì chuyển cùng một đối số, ví dụ Id cho tất cả Hành động. Điều này là do hành động chỉ có thể xác định Id, vì vậy thay vì đặt bất kỳ tên nào cho đối số, chỉ khai báo Id như thế này.


[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 trông như thế nào để xem từng chức năng trong trình duyệt?
Si8

0

Thay thế đơn giản

Chỉ cần sử dụng một chuỗi truy vấn.

định tuyến

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

Bộ điều khiển

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

    public SomeViewModel GetById(int objectId)
    {
    }
}

Yêu cầu

GET /Test
GET /Test?objectId=1

Ghi chú

Hãy nhớ rằng tham số chuỗi truy vấn không được là "id" hoặc bất kỳ tham số nào trong tuyến được định cấu hình.


-1

Sửa đổi WebApiConfig và thêm vào cuối một Routes khác.MapHttpRoute như thế này:

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

Sau đó tạo một bộ điều khiển như thế này:

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

Đây là cách tôi giải quyết nó. Tôi hy vọng nó sẽ giúp được ai đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.