ASP.NET Web API 2에서 특성 라우팅을 사용 하 여 REST API 만들기Create a REST API with Attribute Routing in ASP.NET Web API 2

Mike Wassonby Mike Wasson

Web API 2는 특성 라우팅이라는 새로운 유형의 라우팅을 지원 합니다.Web API 2 supports a new type of routing, called attribute routing. 특성 라우팅에 대 한 일반적인 개요는 WEB API 2에서 특성 라우팅을 참조 하세요.For a general overview of attribute routing, see Attribute Routing in Web API 2. 이 자습서에서는 특성 라우팅을 사용 하 여 책 컬렉션에 대 한 REST API를 만듭니다.In this tutorial, you will use attribute routing to create a REST API for a collection of books. API는 다음 작업을 지원 합니다.The API will support the following actions:

작업Action 예제 URIExample URI
모든 책의 목록을 가져옵니다.Get a list of all books. /api/서적/api/books
ID 별로 책을 가져옵니다.Get a book by ID. /api/books/1/api/books/1
책의 세부 정보를 가져옵니다.Get the details of a book. /api/books/1/details/api/books/1/details
장르별로 책 목록을 가져옵니다.Get a list of books by genre. /api/books/fantasy/api/books/fantasy
게시 날짜별로 책 목록을 가져옵니다.Get a list of books by publication date. /api/books/date/2013-02-16/api/books/date/2013/02/16 (대체 양식)/api/books/date/2013-02-16 /api/books/date/2013/02/16 (alternate form)
특정 저자의 책 목록을 가져옵니다.Get a list of books by a particular author. /api/authors/1/books/api/authors/1/books

모든 메서드는 읽기 전용입니다 (HTTP GET 요청).All methods are read-only (HTTP GET requests).

데이터 계층의 경우 Entity Framework를 사용 합니다.For the data layer, we'll use Entity Framework. 책 레코드에는 다음 필드가 포함 됩니다.Book records will have the following fields:

  • IDID
  • 제목Title
  • GenreGenre
  • 게시 날짜Publication date
  • 가격Price
  • 설명Description
  • AuthorID (외래 키를 Authors 테이블로)AuthorID (foreign key to an Authors table)

그러나 대부분의 요청에서 API는이 데이터의 하위 집합 (제목, 저자 및 장르)을 반환 합니다.For most requests, however, the API will return a subset of this data (title, author, and genre). 전체 레코드를 가져오기 위해 클라이언트는를 요청 /api/books/{id}/details 합니다.To get the complete record, the client requests /api/books/{id}/details.

필수 구성 요소Prerequisites

Visual Studio 2017 Community, Professional 또는 Enterprise edition입니다.Visual Studio 2017 Community, Professional or Enterprise edition.

Visual Studio 프로젝트 만들기Create the Visual Studio Project

Visual Studio를 실행 하 여 시작 합니다.Start by running Visual Studio. 파일 메뉴에서 새로 만들기를 선택한 후 프로젝트를 선택합니다.From the File menu, select New and then select Project.

설치 된 > Visual c # 범주를 확장 합니다.Expand the Installed > Visual C# category. Visual c # 에서 을 선택 합니다.Under Visual C#, select Web. 프로젝트 템플릿 목록에서 ASP.NET 웹 응용 프로그램 (.NET Framework) 을 선택 합니다.In the list of project templates, select ASP.NET Web Application (.NET Framework). 프로젝트 이름을 " booksapi로 " 합니다.Name the project "BooksAPI".

새 ASP.NET 웹 응용 프로그램 대화 상자에서 템플릿을 선택 합니다.In the New ASP.NET Web Application dialog, select the Empty template. "에 대 한 폴더 및 핵심 참조 추가"에서 WEB API 확인란을 선택 합니다.Under "Add folders and core references for", select the Web API checkbox. 확인을 클릭합니다.Click OK.

이렇게 하면 Web API 기능에 대해 구성 된 기본 프로젝트가 생성 됩니다.This creates a skeleton project that is configured for Web API functionality.

도메인 모델Domain Models

다음으로 도메인 모델에 대 한 클래스를 추가 합니다.Next, add classes for domain models. 솔루션 탐색기에서 Models 폴더를 마우스 오른쪽 단추로 클릭합니다.In Solution Explorer, right-click the Models folder. 추가를 선택한 다음 클래스를 선택 합니다.Select Add, then select Class. 클래스 Author 이름을 지정합니다.Name the class Author.

Author.cs의 코드를 다음으로 바꿉니다.Replace the code in Author.cs with the following:

using System.ComponentModel.DataAnnotations;

namespace BooksAPI.Models
{
    public class Author
    {
        public int AuthorId { get; set; }
        [Required]
        public string Name { get; set; }
    }
}

이제 라는 다른 클래스 Book 를 추가 합니다.Now add another class named Book.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BooksAPI.Models
{
    public class Book
    {
        public int BookId { get; set; }
        [Required]
        public string Title { get; set; }
        public decimal Price { get; set; }
        public string Genre { get; set; }
        public DateTime PublishDate { get; set; }
        public string Description { get; set; }
        public int AuthorId { get; set; }
        [ForeignKey("AuthorId")]
        public Author Author { get; set; }
    }
}

Web API 컨트롤러 추가Add a Web API Controller

이 단계에서는 Entity Framework를 데이터 계층으로 사용 하는 Web API 컨트롤러를 추가 합니다.In this step, we'll add a Web API controller that uses Entity Framework as the data layer.

Ctrl+Shift+B를 눌러 프로젝트를 빌드합니다.Press CTRL+SHIFT+B to build the project. Entity Framework는 리플렉션을 사용 하 여 모델의 속성을 검색 하므로 데이터베이스 스키마를 만들기 위해 컴파일된 어셈블리가 필요 합니다.Entity Framework uses reflection to discover the properties of the models, so it requires a compiled assembly to create the database schema.

솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 단추로 클릭합니다.In Solution Explorer, right-click the Controllers folder. 추가를 선택한 다음 컨트롤러를 선택 합니다.Select Add, then select Controller.

스 캐 폴드 추가 대화 상자에서 Entity Framework를 사용 하 여 웹 API 2 컨트롤러 (작업 포함)를 선택 합니다.In the Add Scaffold dialog, select Web API 2 Controller with actions, using Entity Framework.

컨트롤러 추가 대화 상자에서 컨트롤러 이름에 " bookscontroller를 입력 " 합니다.In the Add Controller dialog, for Controller name, enter "BooksController". "비동기 컨트롤러 작업 사용 확인란을 선택 " 합니다.Select the "Use async controller actions" checkbox. 모델 클래스에 대해 Book을 선택 " " 합니다.For Model class, select "Book". 드롭다운에 나열 된 클래스가 표시 되지 않는 경우 Book 프로젝트를 빌드 했는지 확인 합니다. 그런 다음 "+" 단추를 클릭 합니다.(If you don't see the Book class listed in the dropdown, make sure that you built the project.) Then click the "+" button.

새 데이터 컨텍스트 대화 상자에서 추가 를 클릭 합니다.Click Add in the New Data Context dialog.

컨트롤러 추가 대화 상자에서 추가 를 클릭 합니다.Click Add in the Add Controller dialog. 스 캐 폴딩은 BooksController API 컨트롤러를 정의 하는 라는 클래스를 추가 합니다.The scaffolding adds a class named BooksController that defines the API controller. 또한 BooksAPIContext Entity Framework의 데이터 컨텍스트를 정의 하는 모델 폴더에 라는 클래스를 추가 합니다.It also adds a class named BooksAPIContext in the Models folder, which defines the data context for Entity Framework.

데이터베이스 시드Seed the Database

도구 메뉴에서 NuGet 패키지 관리자를 선택한 다음 패키지 관리자 콘솔을 선택 합니다.From the Tools menu, select NuGet Package Manager, and then select Package Manager Console.

패키지 관리자 콘솔 창에서 다음 명령을 입력합니다.In the Package Manager Console window, enter the following command:

Add-Migration

이 명령은 마이그레이션 폴더를 만들고 Configuration.cs 라는 새 코드 파일을 추가 합니다.This command creates a Migrations folder and adds a new code file named Configuration.cs. 이 파일을 열고 다음 코드를 메서드에 추가 합니다 Configuration.Seed .Open this file and add the following code to the Configuration.Seed method.

protected override void Seed(BooksAPI.Models.BooksAPIContext context)
{
    context.Authors.AddOrUpdate(new Author[] {
        new Author() { AuthorId = 1, Name = "Ralls, Kim" },
        new Author() { AuthorId = 2, Name = "Corets, Eva" },
        new Author() { AuthorId = 3, Name = "Randall, Cynthia" },
        new Author() { AuthorId = 4, Name = "Thurman, Paula" }
        });

    context.Books.AddOrUpdate(new Book[] {
        new Book() { BookId = 1,  Title= "Midnight Rain", Genre = "Fantasy", 
        PublishDate = new DateTime(2000, 12, 16), AuthorId = 1, Description =
        "A former architect battles an evil sorceress.", Price = 14.95M }, 

        new Book() { BookId = 2, Title = "Maeve Ascendant", Genre = "Fantasy", 
            PublishDate = new DateTime(2000, 11, 17), AuthorId = 2, Description =
            "After the collapse of a nanotechnology society, the young" +
            "survivors lay the foundation for a new society.", Price = 12.95M },

        new Book() { BookId = 3, Title = "The Sundered Grail", Genre = "Fantasy", 
            PublishDate = new DateTime(2001, 09, 10), AuthorId = 2, Description =
            "The two daughters of Maeve battle for control of England.", Price = 12.95M },

        new Book() { BookId = 4, Title = "Lover Birds", Genre = "Romance", 
            PublishDate = new DateTime(2000, 09, 02), AuthorId = 3, Description =
            "When Carla meets Paul at an ornithology conference, tempers fly.", Price = 7.99M },

        new Book() { BookId = 5, Title = "Splish Splash", Genre = "Romance", 
            PublishDate = new DateTime(2000, 11, 02), AuthorId = 4, Description =
            "A deep sea diver finds true love 20,000 leagues beneath the sea.", Price = 6.99M},
    });
}

패키지 관리자 콘솔 창에서 다음 명령을 입력 합니다.In the Package Manager Console window, type the following commands.

add-migration Initial

update-database

이러한 명령은 로컬 데이터베이스를 만들고 초기값 메서드를 호출 하 여 데이터베이스를 채웁니다.These commands create a local database and invoke the Seed method to populate the database.

DTO 클래스 추가Add DTO Classes

지금 응용 프로그램을 실행 하 고/api/books/1에 GET 요청을 보내면 응답이 다음과 같이 표시 됩니다.If you run the application now and send a GET request to /api/books/1, the response looks similar to the following. (가독성을 위해 들여쓰기를 추가 했습니다.)(I added indentation for readability.)

{
    "BookId": 1,
    "Title": "자정",
    "Genre": "Fantasy",
    "PublishDate": "2000-12-16T00:00:00",
    "Description": "A former architect battles an evil sorceress.",
    "Price": 14.95,
    "AuthorId": 1,
    "Author": null
}

대신이 요청에서 필드의 하위 집합을 반환 하려고 합니다.Instead, I want this request to return a subset of the fields. 작성자 ID 대신 작성자의 이름도 반환 하려고 합니다.Also, I want it to return the author's name, rather than the author ID. 이를 위해 EF 모델 대신 DTO ( 데이터 전송 개체 )를 반환 하도록 컨트롤러 메서드를 수정 합니다.To accomplish this, we'll modify the controller methods to return a data transfer object (DTO) instead of the EF model. DTO은 데이터를 전달 하기 위해 디자인 된 개체입니다.A DTO is an object that is designed only to carry data.

솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 추가 | 새 폴더를 선택 합니다.In Solution Explorer, right-click the project and select Add | New Folder. 폴더 이름을 " dto로 " 합니다.Name the folder "DTOs". 다음 정의를 사용 하 여 라는 클래스를 BookDto dto 폴더에 추가 합니다.Add a class named BookDto to the DTOs folder, with the following definition:

namespace BooksAPI.DTOs
{
    public class BookDto
    {
        public string Title { get; set; }
        public string Author { get; set; }
        public string Genre { get; set; }
    }
}

BookDetailDto(이)라는 다른 클래스를 추가합니다.Add another class named BookDetailDto.

using System;

namespace BooksAPI.DTOs
{
    public class BookDetailDto
    {
        public string Title { get; set; }
        public string Genre { get; set; }
        public DateTime PublishDate { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }         
        public string Author { get; set; }
    }
}

그런 다음 인스턴스를 BooksController 반환 하도록 클래스를 업데이트 합니다 BookDto .Next, update the BooksController class to return BookDto instances. 쿼리할 수 있는 . Select 메서드를 사용 하 여 인스턴스 BookBookDto 인스턴스에 프로젝션 합니다.We'll use the Queryable.Select method to project Book instances to BookDto instances. 컨트롤러 클래스에 대 한 업데이트 된 코드는 다음과 같습니다.Here is the updated code for the controller class.

using BooksAPI.DTOs;
using BooksAPI.Models;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;

namespace BooksAPI.Controllers
{
    public class BooksController : ApiController
    {
        private BooksAPIContext db = new BooksAPIContext();

        // Typed lambda expression for Select() method. 
        private static readonly Expression<Func<Book, BookDto>> AsBookDto =
            x => new BookDto
            {
                Title = x.Title,
                Author = x.Author.Name,
                Genre = x.Genre
            };

        // GET api/Books
        public IQueryable<BookDto> GetBooks()
        {
            return db.Books.Include(b => b.Author).Select(AsBookDto);
        }

        // GET api/Books/5
        [ResponseType(typeof(BookDto))]
        public async Task<IHttpActionResult> GetBook(int id)
        {
            BookDto book = await db.Books.Include(b => b.Author)
                .Where(b => b.BookId == id)
                .Select(AsBookDto)
                .FirstOrDefaultAsync();
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }
        
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Note

PutBook, PostBookDeleteBook 메서드는이 자습서에 필요 하지 않기 때문에 삭제 되었습니다.I deleted the PutBook, PostBook, and DeleteBook methods, because they aren't needed for this tutorial.

이제 응용 프로그램을 실행 하 고/api/books/1를 요청 하면 응답 본문은 다음과 같습니다.Now if you run the application and request /api/books/1, the response body should look like this:

{
    "Title": "자정",
    "Author": "Ralls, Kim",
    "Genre": "Fantasy"
}

경로 특성 추가Add Route Attributes

다음으로, 특성 라우팅을 사용 하도록 컨트롤러를 변환 합니다.Next, we'll convert the controller to use attribute routing. 먼저 컨트롤러에 RoutePrefix 특성을 추가 합니다.First, add a RoutePrefix attribute to the controller. 이 특성은이 컨트롤러의 모든 메서드에 대 한 초기 URI 세그먼트를 정의 합니다.This attribute defines the initial URI segments for all methods on this controller.

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // ...

그런 다음 다음과 같이 [Route] 특성을 컨트롤러 작업에 추가 합니다.Then add [Route] attributes to the controller actions, as follows:

[Route("")]
public IQueryable<BookDto> GetBooks()
{
    // ...
}

[Route("{id:int}")]
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
    // ...
}

각 컨트롤러 메서드의 경로 템플릿은 접두사와 경로 특성에 지정 된 문자열입니다.The route template for each controller method is the prefix plus the string specified in the Route attribute. 메서드의 경우 GetBook 경로 템플릿에는 " " URI 세그먼트에 정수 값이 포함 된 경우와 일치 하는 매개 변수가 있는 문자열 {id: int}가 포함 됩니다.For the GetBook method, the route template includes the parameterized string "{id:int}", which matches if the URI segment contains an integer value.

방법Method 경로 템플릿Route Template 예제 URIExample URI
GetBooks "api/서적""api/books" http://localhost/api/books
GetBook "api/books/{id: int}""api/books/{id:int}" http://localhost/api/books/5

책 정보 가져오기Get Book Details

책 정보를 얻기 위해 클라이언트는에 GET 요청을 보냅니다 /api/books/{id}/details . 여기서 {id} 는 책의 id입니다.To get book details, the client will send a GET request to /api/books/{id}/details, where {id} is the ID of the book.

BooksController 클래스에 다음 메서드를 추가합니다.Add the following method to the BooksController class.

[Route("{id:int}/details")]
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
    var book = await (from b in db.Books.Include(b => b.Author)
                where b.BookId == id
                select new BookDetailDto
                {
                    Title = b.Title,
                    Genre = b.Genre,
                    PublishDate = b.PublishDate,
                    Price = b.Price,
                    Description = b.Description,
                    Author = b.Author.Name
                }).FirstOrDefaultAsync();

    if (book == null)
    {
        return NotFound();
    }
    return Ok(book);
}

를 요청 하 /api/books/1/details 는 경우 응답은 다음과 같습니다.If you request /api/books/1/details, the response looks like this:

{
    "Title": "자정",
    "Genre": "Fantasy",
    "PublishDate": "2000-12-16T00:00:00",
    "Description": "A former architect battles an evil sorceress.",
    "Price": 14.95,
    "Author": "Ralls, Kim"
}

장르 별 서적 가져오기Get Books By Genre

특정 장르에서 책 목록을 가져오기 위해 클라이언트는에 GET 요청을 보냅니다 /api/books/genre . 여기서 장르 는 장르 이름입니다.To get a list of books in a specific genre, the client will send a GET request to /api/books/genre, where genre is the name of the genre. (예: /api/books/fantasy)(For example, /api/books/fantasy.)

에 다음 메서드를 추가 BooksController 합니다.Add the following method to BooksController.

[Route("{genre}")]
public IQueryable<BookDto> GetBooksByGenre(string genre)
{
    return db.Books.Include(b => b.Author)
        .Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
        .Select(AsBookDto);
}

여기서는 URI 템플릿에서 {장르} 매개 변수를 포함 하는 경로를 정의 합니다.Here we are defining a route that contains a {genre} parameter in the URI template. Web API는 이러한 두 Uri를 구분 하 고 다른 메서드로 라우팅할 수 있습니다.Notice that Web API is able to distinguish these two URIs and route them to different methods:

/api/books/1

/api/books/fantasy

이는 메서드는 GetBook "id" 세그먼트가 정수 값 이어야 하는 제약 조건을 포함 하기 때문입니다.That's because the GetBook method includes a constraint that the "id" segment must be an integer value:

[Route("{id:int}")] 
public BookDto GetBook(int id)
{
    // ... 
}

/Api/books/fantasy를 요청 하는 경우 응답은 다음과 같습니다.If you request /api/books/fantasy, the response looks like this:

[ { "Title": "Midnight Rain", "Author": "Ralls, Kim", "Genre": "Fantasy" }, { "Title": "Maeve Ascendant", "Author": "Corets, Eva", "Genre": "Fantasy" }, { "Title": "The Sundered Grail", "Author": "Corets, Eva", "Genre": "Fantasy" } ]

저자 별 책 가져오기Get Books By Author

특정 저자에 대 한 책 목록을 가져오기 위해 클라이언트는에 GET 요청을 보냅니다 /api/authors/id/books . 여기서 id 는 저자의 id입니다.To get a list of a books for a particular author, the client will send a GET request to /api/authors/id/books, where id is the ID of the author.

에 다음 메서드를 추가 BooksController 합니다.Add the following method to BooksController.

[Route("~/api/authors/{authorId:int}/books")]
public IQueryable<BookDto> GetBooksByAuthor(int authorId)
{
    return db.Books.Include(b => b.Author)
        .Where(b => b.AuthorId == authorId)
        .Select(AsBookDto);
}

이 예제는 " 책 " 이 저자 들의 자식 리소스로 처리 되기 때문에 흥미롭습니다 " " .This example is interesting because "books" is treated a child resource of "authors". 이 패턴은 RESTful Api에서 매우 일반적입니다.This pattern is quite common in RESTful APIs.

경로 템플릿의 물결표 (~)는 RoutePrefix 특성의 경로 접두사를 재정의 합니다.The tilde (~) in the route template overrides the route prefix in the RoutePrefix attribute.

게시 날짜별 서적 가져오기Get Books By Publication Date

게시 날짜별로 책 목록을 가져오려면 클라이언트는에 GET 요청을 보냅니다 /api/books/date/yyyy-mm-dd . 여기서 yyyy-mm-dd 는 날짜입니다.To get a list of books by publication date, the client will send a GET request to /api/books/date/yyyy-mm-dd, where yyyy-mm-dd is the date.

이 작업을 수행 하는 한 가지 방법은 다음과 같습니다.Here is one way to do this:

[Route("date/{pubdate:datetime}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
    return db.Books.Include(b => b.Author)
        .Where(b => DbFunctions.TruncateTime(b.PublishDate)
            == DbFunctions.TruncateTime(pubdate))
        .Select(AsBookDto);
}

{pubdate:datetime}매개 변수는 DateTime 값과 일치 하도록 제한 됩니다.The {pubdate:datetime} parameter is constrained to match a DateTime value. 이는 작동 하지만 실제로는 원하는 것 보다 더 허용 됩니다.This works, but it's actually more permissive than we'd like. 예를 들어 다음 Uri는 경로와 일치 합니다.For example, these URIs will also match the route:

/api/books/date/Thu, 01 May 2008

/api/books/date/2000-12-16T00:00:00

이러한 Uri를 허용 하는 것이 잘못 되었습니다.There's nothing wrong with allowing these URIs. 그러나 경로 템플릿에 정규식 제약 조건을 추가 하 여 경로를 특정 형식으로 제한할 수 있습니다.However, you can restrict the route to a particular format by adding a regular-expression constraint to the route template:

[Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
    // ...
}

이제 " yyyy-mm-dd 형식의 날짜만 " 일치 합니다.Now only dates in the form "yyyy-mm-dd" will match. 정규식을 사용 하 여 실제 날짜의 유효성을 검사 하지 않습니다.Notice that we don't use the regex to validate that we got a real date. Web API가 URI 세그먼트를 DateTime 인스턴스로 변환 하려고 할 때 처리 됩니다.That is handled when Web API tries to convert the URI segment into a DateTime instance. ' 2012-47-99 '과 같은 잘못 된 날짜는 변환 되지 않으므로 클라이언트에서 404 오류가 발생 합니다.An invalid date such as '2012-47-99' will fail to be converted, and the client will get a 404 error.

다른 regex를 사용 하 여 /api/books/date/yyyy/mm/dd 다른 [Route] 특성을 추가 하 여 슬래시 구분 기호 ()를 지원할 수도 있습니다.You can also support a slash separator (/api/books/date/yyyy/mm/dd) by adding another [Route] attribute with a different regex.

[Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
[Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]  // new
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
    // ...
}

여기에는 미묘한 중요 한 세부 정보가 있습니다.There is a subtle but important detail here. 두 번째 경로 템플릿의 * {pubdate} 매개 변수 시작 부분에 와일드 카드 문자 ()가 있습니다.The second route template has a wildcard character (*) at the start of the {pubdate} parameter:

{*pubdate: ... }

이렇게 하면 {pubdate}가 URI의 나머지 부분과 일치 해야 한다는 것을 라우팅 엔진에 알립니다.This tells the routing engine that {pubdate} should match the rest of the URI. 기본적으로 템플릿 매개 변수는 단일 URI 세그먼트와 일치 합니다.By default, a template parameter matches a single URI segment. 이 경우에는 {pubdate}를 여러 개의 URI 세그먼트로 확장 하려고 합니다.In this case, we want {pubdate} to span several URI segments:

/api/books/date/2013/06/17

컨트롤러 코드Controller Code

BooksController 클래스에 대 한 전체 코드는 다음과 같습니다.Here is the complete code for the BooksController class.

using BooksAPI.DTOs;
using BooksAPI.Models;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;

namespace BooksAPI.Controllers
{
    [RoutePrefix("api/books")]
    public class BooksController : ApiController
    {
        private BooksAPIContext db = new BooksAPIContext();

        // Typed lambda expression for Select() method. 
        private static readonly Expression<Func<Book, BookDto>> AsBookDto =
            x => new BookDto
            {
                Title = x.Title,
                Author = x.Author.Name,
                Genre = x.Genre
            };

        // GET api/Books
        [Route("")]
        public IQueryable<BookDto> GetBooks()
        {
            return db.Books.Include(b => b.Author).Select(AsBookDto);
        }

        // GET api/Books/5
        [Route("{id:int}")]
        [ResponseType(typeof(BookDto))]
        public async Task<IHttpActionResult> GetBook(int id)
        {
            BookDto book = await db.Books.Include(b => b.Author)
                .Where(b => b.BookId == id)
                .Select(AsBookDto)
                .FirstOrDefaultAsync();
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }

        [Route("{id:int}/details")]
        [ResponseType(typeof(BookDetailDto))]
        public async Task<IHttpActionResult> GetBookDetail(int id)
        {
            var book = await (from b in db.Books.Include(b => b.Author)
                              where b.AuthorId == id
                              select new BookDetailDto
                              {
                                  Title = b.Title,
                                  Genre = b.Genre,
                                  PublishDate = b.PublishDate,
                                  Price = b.Price,
                                  Description = b.Description,
                                  Author = b.Author.Name
                              }).FirstOrDefaultAsync();

            if (book == null)
            {
                return NotFound();
            }
            return Ok(book);
        }

        [Route("{genre}")]
        public IQueryable<BookDto> GetBooksByGenre(string genre)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
                .Select(AsBookDto);
        }

        [Route("~/api/authors/{authorId}/books")]
        public IQueryable<BookDto> GetBooksByAuthor(int authorId)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => b.AuthorId == authorId)
                .Select(AsBookDto);
        }

        [Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
        [Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
        public IQueryable<BookDto> GetBooks(DateTime pubdate)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => DbFunctions.TruncateTime(b.PublishDate)
                    == DbFunctions.TruncateTime(pubdate))
                .Select(AsBookDto);
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

요약Summary

특성 라우팅은 API에 대 한 Uri를 설계할 때 더 많은 제어와 유연성을 제공 합니다.Attribute routing gives you more control and greater flexibility when designing the URIs for your API.