ชื่อเมธอดที่กำหนดเองใน ASP.NET Web API


110

ฉันกำลังแปลงจาก WCF Web API เป็น ASP.NET MVC 4 Web API ใหม่ ฉันมี UsersController และฉันต้องการมีเมธอดชื่อ Authenticate ฉันเห็นตัวอย่างวิธีการทำ GetAll, GetOne, Post และ Delete แต่ถ้าฉันต้องการเพิ่มวิธีการพิเศษในบริการเหล่านี้จะเป็นอย่างไร ตัวอย่างเช่น UsersService ของฉันควรมีเมธอดที่เรียกว่า Authenticate โดยที่พวกเขาส่งผ่านชื่อผู้ใช้และรหัสผ่าน แต่จะไม่ได้ผล

public class UsersController : BaseApiController
{
    public string GetAll()
    {
        return "getall!";
    }

    public string Get(int id)
    {
        return "get 1! " + id;
    }

    public User GetAuthenticate(string userName, string password, string applicationName)
    {
        LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
            userName, password, applicationName));

        //check if valid leapfrog login.
        var decodedUsername = userName.Replace("%40", "@");
        var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
        var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);

        if (leapFrogUsers.Count > 0)
        {
            return new User
            {
                Id = (uint)leapFrogUsers[0].Id,
                Guid = leapFrogUsers[0].Guid
            };
        }
        else
            throw new HttpResponseException("Invalid login credentials");
    }
}

ฉันสามารถเรียกดู myapi / api / users / และมันจะเรียก GetAll และฉันสามารถเรียกดู myapi / api / users / 1 และมันจะเรียก Get แต่ถ้าฉันเรียก myapi / api / users / authenticate? username = {0} & password = {1} จากนั้นจะเรียก Get (NOT Authenticate) และ error:

พจนานุกรมพารามิเตอร์มีรายการ null สำหรับพารามิเตอร์ 'id' ของประเภทที่ไม่เป็นโมฆะ 'System.Int32' สำหรับเมธอด 'System.String Get (Int32)' ใน 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController' พารามิเตอร์ที่เป็นทางเลือกต้องเป็นประเภทการอ้างอิงประเภทที่เป็นโมฆะหรือถูกประกาศเป็นพารามิเตอร์ทางเลือก

ฉันจะเรียกชื่อเมธอดแบบกำหนดเองเช่น Authenticate ได้อย่างไร?


โปรดดูลิงค์นี้: คำตอบที่ 5 stackoverflow.com/questions/12775590/…
Vishwa G

คำตอบ:


137

โดยค่าเริ่มต้นการกำหนดค่าเส้นทางเป็นไปตามข้อกำหนด RESTFul ซึ่งหมายความว่าจะยอมรับเฉพาะชื่อการดำเนินการรับโพสต์ใส่และลบ (ดูที่เส้นทางใน global.asax => โดยค่าเริ่มต้นจะไม่อนุญาตให้คุณระบุชื่อการดำเนินการใด ๆ => ใช้กริยา HTTP เพื่อส่ง) ดังนั้นเมื่อคุณส่งคำขอ GET ถึง/api/users/authenticateคุณโดยทั่วไปจะเรียกการGet(int id)ดำเนินการและส่งผ่านid=authenticateซึ่งเห็นได้ชัดว่าเกิดปัญหาเนื่องจากการดำเนินการ Get ของคุณคาดว่าจะเป็นจำนวนเต็ม

หากคุณต้องการมีชื่อการดำเนินการที่แตกต่างจากชื่อมาตรฐานคุณสามารถแก้ไขนิยามเส้นทางของคุณได้ในglobal.asax:

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

ตอนนี้คุณสามารถไปที่/api/values/getauthenticateเพื่อตรวจสอบผู้ใช้


20
มีวิธีทำให้ยังคงใช้ Get (id), Get () Put, Delete, Post ในขณะที่ยังอนุญาตให้ดำเนินการอื่น ๆ ได้หรือไม่?
Shawn Mclean

@ShawnMclean ฉันเดาว่าคุณสามารถระบุเส้นทางอื่นได้โดย{action}ที่ไม่มีข้อ จำกัด{id}ดังนั้นสิ่งอื่นที่ไม่ใช่intหรือGuid(หรืออะไรก็ตาม) จะไม่ตรงกัน จากนั้นก็ควรจะสามารถผ่านไปถึงคำแนะนำโดย Darin
Steve Greatrex

2
สิ่งที่สำคัญอีกอย่างก็คือด้วยการกำหนดเส้นทางรูปแบบนี้คุณต้องใช้แอตทริบิวต์เพื่อระบุวิธี HTTP ที่อนุญาต (เช่น [HttpGet])
Himalaya Garg

1
แน่ใจหรือว่าต้องใช้การกระทำอื่น ๆ คุณพยายามปรับตัวให้เข้ากับสิ่งที่คุณกำลังทำในอนุสัญญา REST หรือไม่? ไม่ควรจำเป็นต้องใช้การกระทำอื่น ๆ
niico

1
@niico: ลองนึกภาพว่าคุณต้องการมีวิธี Count () ที่ส่งคืนจำนวนองค์ประกอบที่ Get () จะส่งกลับ ฉันไม่เห็นว่าจะใส่มันลงใน Get (), Get (id), Post (... ), Put (... ) หรือ Delete (id) ได้อย่างไร และแน่นอนว่ามีวิธีการที่เป็นไปได้มากกว่าที่ฉันจะจินตนาการได้
Jens Mander

88

นี่เป็นวิธีที่ดีที่สุดที่ฉันได้คิดขึ้นมาเพื่อรวมวิธีการ 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 ให้กับวิธีการ


1
ทางออกที่ดีคุณช่วยบอกฉันได้ไหมว่าฉันกำหนดค่าputและdeleteคำกริยาเช่นที่คุณทำgetและpostจะทำงานได้ดีด้วยหรือไม่?
Felipe Oriani

1
ในความคิดของฉันสิ่งนี้ควรรวมอยู่ในค่าเริ่มต้นสำหรับโครงการ WebAPI (อาจแสดงความคิดเห็น) ให้เส้นทางสไตล์ WebAPI และ MVC ในเวลาเดียวกัน ...
John Culviner

1
@FelipeOriani ฉันไม่คิดว่าคุณจะต้องการหรือจำเป็นต้องกำหนดค่าputหรือdeleteคำกริยาเนื่องจากโดยปกติแล้วคำขอเหล่านั้นจะมาพร้อมกับพารามิเตอร์ id เพื่อระบุทรัพยากรที่คุณต้องการใช้การดำเนินการนั้น การdeleteเรียกร้องให้/api/fooเกิดข้อผิดพลาดเนื่องจากคุณพยายามลบ foo ตัวใด ดังนั้นเส้นทาง DefaultApiWithId ควรจัดการกรณีเหล่านั้นได้ดี
nwayve

4
สิ่งนี้ไม่ได้ผลสำหรับฉันเลย ได้รับข้อความแสดงข้อผิดพลาดเมื่อฉันพยายามทำ GET พื้นฐาน
แมต

สำหรับอันแรก DefaultApiWithId ค่าเริ่มต้นไม่ควรเป็นโมฆะแทนที่จะเป็น {id = RouteParameter.Optional} ใหม่ ไม่จำเป็นต้องมี 'id'?
Johnny Oshika

22

ฉันเป็นวันที่เข้าสู่โลก MVC4

สำหรับสิ่งที่คุ้มค่าฉันมี SitesAPIController และฉันต้องการวิธีการที่กำหนดเองซึ่งอาจเรียกได้ว่า:

http://localhost:9000/api/SitesAPI/Disposition/0

ด้วยค่าที่แตกต่างกันสำหรับพารามิเตอร์สุดท้ายเพื่อรับเรกคอร์ดที่มีการจัดการต่างกัน

สิ่งที่ได้ผลสำหรับฉันในที่สุดคือ:

วิธีการใน SitesAPIController:

// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
    Site site = db.Sites.Where(s => s.Disposition == disposition).First();
    return site;
}

และสิ่งนี้ใน WebApiConfig.cs

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

// this i added
config.Routes.MapHttpRoute(
    name: "Action",
    routeTemplate: "api/{controller}/{action}/{disposition}"
 );

ตราบเท่าที่ฉันตั้งชื่อ {การจัดการ} เป็น {id} ฉันพบ:

{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}

เมื่อฉันเปลี่ยนชื่อเป็น {การจัดการ} มันเริ่มทำงาน เห็นได้ชัดว่าชื่อพารามิเตอร์ตรงกับค่าในตัวยึด

อย่าลังเลที่จะแก้ไขคำตอบนี้เพื่อให้ถูกต้อง / อธิบายได้มากขึ้น


ขอบคุณสำหรับทิป. ฉันทำผิดพลาดเช่นเดียวกับคุณ
abhi

16

โดยค่าเริ่มต้น Web Api จะคาดหวัง URL ในรูปแบบของ api / {controller} / {id} เพื่อแทนที่การกำหนดเส้นทางเริ่มต้นนี้ คุณสามารถกำหนดเส้นทางด้วยวิธีใดก็ได้จากสองวิธีด้านล่างนี้

ตัวเลือกแรก:

เพิ่มการลงทะเบียนเส้นทางด้านล่างใน WebApiConfig.cs

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

ตกแต่งวิธีการดำเนินการของคุณด้วย HttpGet และพารามิเตอร์ด้านล่าง

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

 {

// your code here

}

สำหรับการเรียก method url ข้างต้นจะเป็นดังนี้

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

ตัวเลือกที่สอง เพิ่มคำนำหน้าเส้นทางไปยังคลาสคอนโทรลเลอร์และตกแต่งวิธีการดำเนินการของคุณด้วย HttpGet ดังต่อไปนี้ ในกรณีนี้ไม่จำเป็นต้องเปลี่ยน WebApiConfig.cs สามารถมีการกำหนดเส้นทางเริ่มต้น

[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

{

// your code here

}

}

สำหรับการเรียก method url ข้างต้นจะเป็นดังนี้

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3


ฉันชอบตัวเลือกที่สองมาก คุณช่วยแสดงวิธีใช้ใน VB.net ได้ไหม ขอบคุณมาก.
user1617676

12

ในกรณีที่คุณใช้ASP.NET 5กับASP.NET MVC 6คำตอบเหล่านี้ส่วนใหญ่จะใช้ไม่ได้เพราะโดยปกติคุณจะปล่อยให้ MVC สร้างคอลเลคชันเส้นทางที่เหมาะสมสำหรับคุณ (โดยใช้ข้อตกลง RESTful เริ่มต้น) ซึ่งหมายความว่า คุณจะไม่พบการRoutes.MapRoute()เรียกให้แก้ไขตามต้องการ

ConfigureServices()วิธีการเรียกใช้โดยStartup.csไฟล์จะลงทะเบียน MVC กับกรอบการพึ่งพาการฉีดที่สร้างขึ้นใน ASP.NET ที่ 5: วิธีการที่เมื่อคุณเรียกApplicationBuilder.UseMvc()ต่อมาในชั้นเรียนนั้นกรอบ MVC โดยอัตโนมัติจะเพิ่มเส้นทางเริ่มต้นเหล่านี้ไปยังแอป เราสามารถดูสิ่งที่เกิดขึ้นหลังฝากระโปรงได้โดยดูที่ไฟล์UseMvc()การใช้งานวิธีการภายในซอร์สโค้ดของเฟรมเวิร์ก:

public static IApplicationBuilder UseMvc(
    [NotNull] this IApplicationBuilder app,
    [NotNull] Action<IRouteBuilder> configureRoutes)
{
    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);

    var routes = new RouteBuilder
    {
        DefaultHandler = new MvcRouteHandler(),
        ServiceProvider = app.ApplicationServices
    };

    configureRoutes(routes);

    // Adding the attribute route comes after running the user-code because
    // we want to respect any changes to the DefaultHandler.
    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
        routes.DefaultHandler,
        app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

สิ่งที่ดีเกี่ยวกับเรื่องนี้คือตอนนี้เฟรมเวิร์กจัดการงานหนักทั้งหมดทำซ้ำผ่านการดำเนินการทั้งหมดของคอนโทรลเลอร์และการตั้งค่าเส้นทางเริ่มต้นซึ่งช่วยให้คุณประหยัดงานที่ซ้ำซ้อน

สิ่งที่แย่คือมีเอกสารเพียงเล็กน้อยหรือไม่มีเลยเกี่ยวกับวิธีเพิ่มเส้นทางของคุณเอง โชคดีที่คุณสามารถทำได้อย่างง่ายดายโดยใช้วิธีการตามอนุสัญญาและ / หรือวิธีการตามแอตทริบิวต์ (aka Attribute Routing )

ตามอนุสัญญา

ในคลาส Startup.cs ของคุณให้แทนที่สิ่งนี้:

app.UseMvc();

ด้วยสิ่งนี้:

app.UseMvc(routes =>
            {
                // Route Sample A
                routes.MapRoute(
                    name: "RouteSampleA",
                    template: "MyOwnGet",
                    defaults: new { controller = "Items", action = "Get" }
                );
                // Route Sample B
                routes.MapRoute(
                    name: "RouteSampleB",
                    template: "MyOwnPost",
                    defaults: new { controller = "Items", action = "Post" }
                );
            });

ตามคุณสมบัติ

สิ่งที่ยอดเยี่ยมเกี่ยวกับ MVC6 คือคุณสามารถกำหนดเส้นทางบนพื้นฐานสำหรับแต่ละคอนโทรลเลอร์ได้โดยการตกแต่งControllerคลาสและ / หรือActionวิธีการด้วยพารามิเตอร์ที่เหมาะสมRouteAttributeและ / หรือHttpGet/ HttpPostเทมเพลตดังต่อไปนี้:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace MyNamespace.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        // GET: api/items
        [HttpGet()]
        public IEnumerable<string> Get()
        {
            return GetLatestItems();
        }

        // GET: api/items/5
        [HttpGet("{num}")]
        public IEnumerable<string> Get(int num)
        {
            return GetLatestItems(5);
        }       

        // GET: api/items/GetLatestItems
        [HttpGet("GetLatestItems")]
        public IEnumerable<string> GetLatestItems()
        {
            return GetLatestItems(5);
        }

        // GET api/items/GetLatestItems/5
        [HttpGet("GetLatestItems/{num}")]
        public IEnumerable<string> GetLatestItems(int num)
        {
            return new string[] { "test", "test2" };
        }

        // POST: /api/items/PostSomething
        [HttpPost("PostSomething")]
        public IActionResult Post([FromBody]string someData)
        {
            return Content("OK, got it!");
        }
    }
}

ตัวควบคุมนี้จะจัดการกับคำขอต่อไปนี้:

 [GET] api/items
 [GET] api/items/5
 [GET] api/items/GetLatestItems
 [GET] api/items/GetLatestItems/5
 [POST] api/items/PostSomething

นอกจากนี้โปรดสังเกตว่าหากคุณใช้ togheter สองวิธีเส้นทางตามแอตทริบิวต์ (เมื่อกำหนด) จะแทนที่เส้นทางที่อิงตามอนุสัญญาและทั้งสองวิธีจะแทนที่เส้นทางเริ่มต้นที่กำหนดโดย UseMvc()และทั้งสองของพวกเขาจะแทนที่เส้นทางเริ่มต้นที่กำหนดโดย

สำหรับข้อมูลเพิ่มเติมคุณสามารถอ่านโพสต์ต่อไปนี้ในบล็อกของฉัน


1
ลงตัวนี้! ไม่มีคำตอบอื่นใดที่ทำได้จริงอย่างที่ฉันต้องการ แต่คุณช่วยฉันไว้ :)
King Arthur the Third

มีวิธีใช้โมเดลที่กำหนดไว้ล่วงหน้าเป็นพารามิเตอร์ที่สองหรือไม่? ตัวอย่างเช่นเมื่อฉัน patching ผู้ใช้บางอย่างเช่นนี้ public IActionResult Patch(int id, [FromQuery] Person person), คุณสมบัติที่เข้ามาทั้งหมดเป็นโมฆะ!
King Arthur the Third

9

ดูบทความนี้สำหรับการอภิปรายอีกต่อไปเกี่ยวกับการกระทำที่ตั้งชื่อ นอกจากนี้ยังแสดงให้เห็นว่าคุณสามารถใช้แอตทริบิวต์ [HttpGet] แทนการขึ้นต้นชื่อการกระทำด้วย "get"

http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api


0

เพียงแค่ปรับเปลี่ยน WebAPIConfig.cs ของคุณดังต่อไปนี้

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

จากนั้นใช้ API ของคุณดังต่อไปนี้

    // GET: api/Controller_Name/Show/1
    [ActionName("Show")]
    [HttpGet]
    public EventPlanner Id(int id){}

0

Web APi 2 และเวอร์ชันที่ใหม่กว่ารองรับการกำหนดเส้นทางประเภทใหม่เรียกว่าการกำหนดเส้นทางแอตทริบิวต์ ตามความหมายของชื่อการกำหนดเส้นทางแอตทริบิวต์ใช้แอตทริบิวต์เพื่อกำหนดเส้นทาง การกำหนดเส้นทางแอตทริบิวต์ช่วยให้คุณควบคุม URI ใน Web API ได้มากขึ้น ตัวอย่างเช่นคุณสามารถสร้าง URI ที่อธิบายลำดับชั้นของทรัพยากรได้อย่างง่ายดาย

ตัวอย่างเช่น:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

จะสมบูรณ์แบบและคุณไม่จำเป็นต้องใช้รหัสพิเศษใด ๆ เช่นใน WebApiConfig.cs เพียงแค่คุณต้องแน่ใจว่าเปิดใช้งานการกำหนดเส้นทางเว็บ api หรือไม่ใน WebApiConfig.cs หากไม่สามารถเปิดใช้งานได้ดังนี้:

        // Web API routes
        config.MapHttpAttributeRoutes();

คุณไม่จำเป็นต้องทำอะไรเพิ่มเติมหรือเปลี่ยนแปลงบางอย่างใน WebApiConfig.cs สำหรับรายละเอียดเพิ่มเติมสามารถดูได้ในบทความนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.