Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Web-API 2 unterstützt einen neuen Routingtyp, der als Attributrouting bezeichnet wird. Eine allgemeine Übersicht über das Attributrouting finden Sie unter Attributrouting in Web API 2. In diesem Lernprogramm verwenden Sie das Attributrouting, um eine REST-API für eine Sammlung von Büchern zu erstellen. Die API unterstützt die folgenden Aktionen:
| Action | Beispiel-URI |
|---|---|
| Dient zum Abrufen einer Liste aller Bücher. | /api/books |
| Abrufen eines Buchs nach ID. | /api/books/1 |
| Rufen Sie die Details eines Buchs ab. | /api/books/1/details |
| Dient zum Abrufen einer Liste von Büchern nach Genre. | /api/books/fantasy |
| Dient zum Abrufen einer Liste von Büchern nach Veröffentlichungsdatum. | /api/books/date/2013-02-16 /api/books/date/2013/02/16 (alternatives Formular) |
| Dient zum Abrufen einer Liste von Büchern durch einen bestimmten Autor. | /api/authors/1/books |
Alle Methoden sind schreibgeschützt (HTTP GET-Anforderungen).
Für die Datenebene verwenden wir Entity Framework. Buchdatensätze weisen die folgenden Felder auf:
- ID
- Title
- Genre
- Veröffentlichungsdatum
- Preis
- Beschreibung
- AuthorID (Fremdschlüssel für eine Authors-Tabelle)
Für die meisten Anforderungen gibt die API jedoch eine Teilmenge dieser Daten zurück (Titel, Autor und Genre). Um den vollständigen Datensatz abzurufen, fordert der Client an /api/books/{id}/details.
Voraussetzungen
Visual Studio 2017 Community-, Professional- oder Enterprise-Edition.
Erstellen des Visual Studio-Projekts
Führen Sie zunächst Visual Studio aus. Wählen Sie im Menü Datei die Option Neuaus, und wählen Sie dann Projekt aus.
Erweitern Sie die Kategorie "Installierte>Visual C#" . Wählen Sie unter Visual C#"Web" aus. Wählen Sie in der Liste der Projektvorlagen ASP.NET Webanwendung (.NET Framework) aus. Nennen Sie das Projekt "BooksAPI".
Wählen Sie im Dialogfeld "Neue ASP.NET Webanwendung " die Vorlage "Leer " aus. Aktivieren Sie unter "Ordner und Kernverweise hinzufügen für" das Kontrollkästchen "Web-API ". Klicke auf OK.
Dadurch wird ein Skelettprojekt erstellt, das für die Web-API-Funktionalität konfiguriert ist.
Domänenmodelle
Fügen Sie als Nächstes Klassen für Domänenmodelle hinzu. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner "Modelle". Wählen Sie "Hinzufügen" und dann " Kurs" aus. Benennen Sie die Klasse Author.
Ersetzen Sie den Code in Author.cs durch Folgendes:
using System.ComponentModel.DataAnnotations;
namespace BooksAPI.Models
{
public class Author
{
public int AuthorId { get; set; }
[Required]
public string Name { get; set; }
}
}
Fügen Sie nun eine weitere Klasse mit dem Namen Bookhinzu.
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; }
}
}
Hinzufügen eines Web-API-Controllers
In diesem Schritt fügen wir einen Web-API-Controller hinzu, der Entity Framework als Datenebene verwendet.
Drücken Sie STRG+UMSCHALT+B, um das Projekt zu erstellen. Entity Framework verwendet Reflektion, um die Eigenschaften der Modelle zu ermitteln, sodass eine kompilierte Assembly zum Erstellen des Datenbankschemas erforderlich ist.
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner "Controller". Wählen Sie "Hinzufügen" und dann "Controller" aus.
Wählen Sie im Dialogfeld " Gerüst hinzufügen " den Web-API 2-Controller mit Aktionen mithilfe von Entity Framework aus.
Geben Sie im Dialogfeld " Controller hinzufügen " für den Controllernamen "BooksController" ein. Aktivieren Sie das Kontrollkästchen "Asynchrone Controlleraktionen verwenden". Wählen Sie für die Model-Klasse "Buch" aus. (Wenn die Book Im Dropdownliste aufgeführte Klasse nicht angezeigt wird, stellen Sie sicher, dass Sie das Projekt erstellt haben.) Klicken Sie dann auf die Schaltfläche "+".
Klicken Sie im Dialogfeld "Neuer Datenkontext" auf "Hinzufügen".
Klicken Sie im Dialogfeld "Controller hinzufügen" auf "Hinzufügen". Das Gerüst fügt eine Klasse mit dem Namen BooksController hinzu, die den API-Controller definiert. Außerdem wird im Ordner "Models" eine Klasse hinzugefügt BooksAPIContext , die den Datenkontext für Entity Framework definiert.
Seeden der Datenbank
Wählen Sie im Menü "Extras" die Option "NuGet-Paket-Manager" und dann die Paket-Manager-Konsole aus.
Geben Sie im Paket-Manager-Konsolenfenster den folgenden Befehl ein:
Add-Migration
Dieser Befehl erstellt einen Migrationsordner und fügt eine neue Codedatei mit dem Namen Configuration.cs hinzu. Öffnen Sie diese Datei, und fügen Sie der Configuration.Seed Methode den folgenden Code hinzu.
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},
});
}
Geben Sie im Paket-Manager-Konsolenfenster die folgenden Befehle ein.
add-migration Initial
update-database
Diese Befehle erstellen eine lokale Datenbank und rufen die Seed-Methode auf, um die Datenbank aufzufüllen.
Hinzufügen von DTO-Klassen
Wenn Sie die Anwendung jetzt ausführen und eine GET-Anforderung an /api/books/1 senden, sieht die Antwort ähnlich wie folgt aus. (Ich habe einen Einzug zur Lesbarkeit hinzugefügt.)
{
"BookId": 1,
"Title": "Midnight Rain",
"Genre": "Fantasy",
"PublishDate": "2000-12-16T00:00:00",
"Description": "A former architect battles an evil sorceress.",
"Price": 14.95,
"AuthorId": 1,
"Author": null
}
Stattdessen möchte ich, dass diese Anforderung eine Teilmenge der Felder zurückgibt. Außerdem möchte ich, dass er den Namen des Autors und nicht die Autoren-ID zurückgibt. Dazu ändern wir die Controllermethoden so, dass anstelle des EF-Modells ein DTO (Data Transfer Object ) zurückgegeben wird. Ein DTO ist ein Objekt, das nur zum Übertragen von Daten konzipiert ist.
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie"Neuen Ordnerhinzufügen | " aus. Nennen Sie den Ordner "DTOs". Fügen Sie dem DTOs-Ordner eine Klasse mit der folgenden Definition hinzu BookDto :
namespace BooksAPI.DTOs
{
public class BookDto
{
public string Title { get; set; }
public string Author { get; set; }
public string Genre { get; set; }
}
}
Fügen Sie eine weitere Klasse mit dem Namen BookDetailDtohinzu.
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; }
}
}
Aktualisieren Sie als Nächstes die BooksController Klasse, um Instanzen zurückzugeben BookDto . Wir verwenden die Queryable.Select-Methode zum Projizieren Book von BookDto Instanzen. Hier sehen Sie den aktualisierten Code für die Controllerklasse.
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);
}
}
}
Hinweis
Ich habe die PutBookMethoden PostBookund DeleteBook Methoden gelöscht, da sie für dieses Lernprogramm nicht benötigt werden.
Wenn Sie nun die Anwendung ausführen und /api/books/1 anfordern, sollte der Antworttext wie folgt aussehen:
{"Title":"Midnight Rain","Author":"Ralls, Kim","Genre":"Fantasy"}
Hinzufügen von Routenattributen
Als Nächstes konvertieren wir den Controller in die Verwendung des Attributroutings. Fügen Sie zunächst dem Controller ein RoutePrefix-Attribut hinzu. Dieses Attribut definiert die anfänglichen URI-Segmente für alle Methoden auf diesem Controller.
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// ...
Fügen Sie dann [Route] -Attribute zu den Controlleraktionen wie folgt hinzu:
[Route("")]
public IQueryable<BookDto> GetBooks()
{
// ...
}
[Route("{id:int}")]
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
// ...
}
Die Routenvorlage für jede Controllermethode ist das Präfix plus die im Route-Attribut angegebene Zeichenfolge. Für die GetBook Methode enthält die Routenvorlage die parametrisierte Zeichenfolge "{id:int}", die übereinstimmt, wenn das URI-Segment einen ganzzahligen Wert enthält.
| Methode | Routenvorlage | Beispiel-URI |
|---|---|---|
GetBooks |
"api/books" | http://localhost/api/books |
GetBook |
"api/books/{id:int}" | http://localhost/api/books/5 |
Buchdetails abrufen
Um Buchdetails abzurufen, sendet der Client eine GET-Anforderung an /api/books/{id}/details, wobei {id} die ID des Buchs ist.
Fügen Sie der BooksController-Klasse die folgende Methode hinzu.
[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);
}
Wenn Sie eine Anforderung anfordern /api/books/1/details, sieht die Antwort wie folgt aus:
{
"Title": "Midnight Rain",
"Genre": "Fantasy",
"PublishDate": "2000-12-16T00:00:00",
"Description": "A former architect battles an evil sorceress.",
"Price": 14.95,
"Author": "Ralls, Kim"
}
Bücher nach Genre abrufen
Um eine Liste von Büchern in einem bestimmten Genre zu erhalten, sendet der Client eine GET-Anforderung an /api/books/genre, wo Genre der Name des Genres ist. (Beispiel: /api/books/fantasy.)
Fügen Sie die folgende Methode zu 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);
}
Hier definieren wir eine Route, die einen {genre}-Parameter in der URI-Vorlage enthält. Beachten Sie, dass die Web-API diese beiden URIs unterscheiden und an verschiedene Methoden weiterleiten kann:
/api/books/1
/api/books/fantasy
Dies liegt daran, dass die GetBook Methode eine Einschränkung enthält, dass das Segment "id" ein ganzzahliger Wert sein muss:
[Route("{id:int}")]
public BookDto GetBook(int id)
{
// ...
}
Wenn Sie /api/books/fantasy anfordern, sieht die Antwort wie folgt aus:
[ { "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" } ]
Bücher nach Autor abrufen
Um eine Liste von Büchern für einen bestimmten Autor abzurufen, sendet der Client eine GET-Anforderung an /api/authors/id/books, wobei die ID der Autor ist.
Fügen Sie die folgende Methode zu 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);
}
Dieses Beispiel ist interessant, da "Bücher" eine untergeordnete Ressource von "Autoren" behandelt wird. Dieses Muster ist in RESTful-APIs ziemlich üblich.
Die Tilde (~) in der Routenvorlage überschreibt das Routenpräfix im RoutePrefix-Attribut .
Bücher nach Veröffentlichungsdatum abrufen
Um eine Liste von Büchern nach Veröffentlichungsdatum zu erhalten, sendet der Client eine GET-Anforderung an /api/books/date/yyyy-mm-dd, wobei jjjj-mm-dd das Datum ist.
Hier ist eine Möglichkeit, dies zu tun:
[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);
}
Der {pubdate:datetime} Parameter ist auf einen DateTime-Wert beschränkt. Dies funktioniert, aber es ist tatsächlich weniger zulässig als wir möchten. Diese URIs stimmen beispielsweise auch mit der Route überein:
/api/books/date/Thu, 01 May 2008
/api/books/date/2000-12-16T00:00:00
Es gibt nichts Falsches beim Zulassen dieser URIs. Sie können die Route jedoch auf ein bestimmtes Format beschränken, indem Sie der Routenvorlage eine Einschränkung für reguläre Ausdrücke hinzufügen:
[Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
// ...
}
Jetzt stimmen nur Datumsangaben im Format "yyyy-mm-dd" überein. Beachten Sie, dass wir den regex nicht verwenden, um zu überprüfen, ob wir ein echtes Datum erhalten haben. Dies wird behandelt, wenn die Web-API versucht, das URI-Segment in eine DateTime-Instanz zu konvertieren. Ein ungültiges Datum wie "2012-47-99" kann nicht konvertiert werden, und der Client erhält einen Fehler von 404.
Sie können auch ein Schrägstrichtrennzeichen (/api/books/date/yyyy/mm/dd) unterstützen, indem Sie ein weiteres [Route] -Attribut mit einem anderen Regex hinzufügen.
[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)
{
// ...
}
Hier gibt es ein subtiles, aber wichtiges Detail. Die zweite Routenvorlage weist am Anfang des {pubdate}-Parameters ein Platzhalterzeichen (*) auf:
{*pubdate: ... }
Dadurch wird dem Routingmodul mitgeteilt, dass {pubdate} mit dem rest des URI übereinstimmen soll. Standardmäßig entspricht ein Vorlagenparameter einem einzelnen URI-Segment. In diesem Fall soll {pubdate} mehrere URI-Segmente umfassen:
/api/books/date/2013/06/17
Controllercode
Hier ist der vollständige Code für die BooksController-Klasse.
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);
}
}
}
Zusammenfassung
Das Attributrouting bietet Ihnen mehr Kontrolle und mehr Flexibilität beim Entwerfen der URIs für Ihre API.