Obsługa relacji jednostek w usłudze OData w wersji 3 za pomocą internetowego interfejsu API 2
Autor: Mike Wasson
Pobieranie ukończonego projektu
Większość zestawów danych definiuje relacje między jednostkami: klienci mają zamówienia; książki mają autorów; produkty mają dostawców. Za pomocą usługi OData klienci mogą nawigować po relacjach jednostek. Biorąc pod uwagę produkt, możesz znaleźć dostawcę. Można również tworzyć lub usuwać relacje. Na przykład można ustawić dostawcę produktu.
W tym samouczku pokazano, jak obsługiwać te operacje w ASP.NET internetowym interfejsie API. Samouczek opiera się na samouczku Tworzenie punktu końcowego OData w wersji 3 za pomocą internetowego interfejsu API 2.
Wersje oprogramowania używane w samouczku
- Internetowy interfejs API 2
- OData w wersji 3
- Entity Framework 6
Dodawanie jednostki dostawcy
Najpierw musimy dodać nowy typ jednostki do źródła danych OData. Dodamy klasę Supplier
.
using System.ComponentModel.DataAnnotations;
namespace ProductService.Models
{
public class Supplier
{
[Key]
public string Key { get; set; }
public string Name { get; set; }
}
}
Ta klasa używa ciągu dla klucza jednostki. W praktyce może to być mniej powszechne niż użycie klucza całkowitego. Warto jednak zobaczyć, jak funkcja OData obsługuje inne typy kluczy oprócz liczb całkowitych.
Następnie utworzymy relację, dodając Supplier
właściwość do Product
klasy:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
// New code
[ForeignKey("Supplier")]
public string SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
Dodaj nowy zestaw dbSet do ProductServiceContext
klasy, aby program Entity Framework zawierał tabelę Supplier
w bazie danych.
public class ProductServiceContext : DbContext
{
public ProductServiceContext() : base("name=ProductServiceContext")
{
}
public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
// New code:
public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}
W pliku WebApiConfig.cs dodaj jednostkę "Dostawcy" do modelu EDM:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");
Właściwości nawigacji
Aby uzyskać dostawcę produktu, klient wysyła żądanie GET:
GET /Products(1)/Supplier
Tutaj "Dostawca" jest właściwością nawigacji w typie Product
. W tym przypadku Supplier
odnosi się do pojedynczego elementu, ale właściwość nawigacji może również zwrócić kolekcję (relacja jeden do wielu lub wiele do wielu).
Aby obsługiwać to żądanie, dodaj następującą metodę ProductsController
do klasy:
// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
Product product = _context.Products.FirstOrDefault(p => p.ID == key);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product.Supplier;
}
Parametr klucza jest kluczem produktu. Metoda zwraca powiązaną Supplier
jednostkę — w tym przypadku wystąpienie. Nazwa metody i nazwa parametru są ważne. Ogólnie rzecz biorąc, jeśli właściwość nawigacji ma nazwę "X", należy dodać metodę o nazwie "GetX". Metoda musi przyjmować parametr o nazwie "key", który odpowiada typowi danych klucza nadrzędnego.
Ważne jest również, aby uwzględnić atrybut [FromOdataUri] w parametrze klucza . Ten atrybut informuje internetowy interfejs API o korzystaniu z reguł składni OData podczas analizowania klucza z identyfikatora URI żądania.
Tworzenie i usuwanie łączy
Usługa OData obsługuje tworzenie lub usuwanie relacji między dwiema jednostkami. W terminologii OData relacja jest "łączem". Każdy link ma identyfikator URI z jednostką formularza/$links/jednostką. Na przykład link z produktu do dostawcy wygląda następująco:
/Products(1)/$links/Supplier
Aby utworzyć nowy link, klient wysyła żądanie POST do identyfikatora URI linku. Treść żądania jest identyfikatorem URI jednostki docelowej. Załóżmy na przykład, że istnieje dostawca z kluczem "CTSO". Aby utworzyć link z "Product(1)" do "Supplier('CTSO')", klient wysyła żądanie podobne do następującego:
POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50
{"url":"http://localhost/odata/Suppliers('CTSO')"}
Aby usunąć link, klient wysyła żądanie DELETE do identyfikatora URI linku.
Tworzenie łączy
Aby umożliwić klientowi tworzenie linków dostawców produktów, dodaj następujący kod do ProductsController
klasy:
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
string supplierKey = GetKeyFromLinkUri<string>(link);
Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
if (supplier == null)
{
return NotFound();
}
product.Supplier = supplier;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
Ta metoda przyjmuje trzy parametry:
- klucz: klucz do jednostki nadrzędnej (produktu)
- navigationProperty: nazwa właściwości nawigacji. W tym przykładzie jedyną prawidłową właściwością nawigacji jest "Dostawca".
- link: identyfikator URI OData powiązanej jednostki. Ta wartość jest pobierana z treści żądania. Na przykład identyfikator URI łącza może być "
http://localhost/odata/Suppliers('CTSO')
, co oznacza, że dostawca o identyfikatorze = 'CTSO'.
Metoda używa linku, aby wyszukać dostawcę. Jeśli zostanie znaleziony pasujący dostawca, metoda ustawia Product.Supplier
właściwość i zapisuje wynik w bazie danych.
Najtrudniejszą częścią jest analizowanie identyfikatora URI łącza. Zasadniczo należy symulować wynik wysyłania żądania GET do tego identyfikatora URI. Poniższa metoda pomocnika pokazuje, jak to zrobić. Metoda wywołuje proces routingu internetowego interfejsu API i zwraca wystąpienie ODataPath reprezentujące przeanalizowaną ścieżkę ODataData. W przypadku identyfikatora URI łącza jeden z segmentów powinien być kluczem jednostki. (Jeśli nie, klient wysłał nieprawidłowy identyfikator URI).
// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
TKey key = default(TKey);
// Get the route that was used for this request.
IHttpRoute route = Request.GetRouteData().Route;
// Create an equivalent self-hosted route.
IHttpRoute newRoute = new HttpRoute(route.RouteTemplate,
new HttpRouteValueDictionary(route.Defaults),
new HttpRouteValueDictionary(route.Constraints),
new HttpRouteValueDictionary(route.DataTokens), route.Handler);
// Create a fake GET request for the link URI.
var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);
// Send this request through the routing process.
var routeData = newRoute.GetRouteData(
Request.GetConfiguration().VirtualPathRoot, tmpRequest);
// If the GET request matches the route, use the path segments to find the key.
if (routeData != null)
{
ODataPath path = tmpRequest.GetODataPath();
var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
if (segment != null)
{
// Convert the segment into the key type.
key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
segment.Value, ODataVersion.V3);
}
}
return key;
}
Usuwanie łączy
Aby usunąć link, dodaj następujący kod do ProductsController
klasy:
public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
product.Supplier = null;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
W tym przykładzie właściwość nawigacji jest pojedynczą Supplier
jednostką. Jeśli właściwość nawigacji jest kolekcją, identyfikator URI do usunięcia linku musi zawierać klucz dla powiązanej jednostki. Na przykład:
DELETE /odata/Customers(1)/$links/Orders(1)
To żądanie usuwa zamówienie 1 od klienta 1. W takim przypadku metoda DeleteLink będzie miała następujący podpis:
void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);
Parametr relatedKey udostępnia klucz powiązanej jednostki. Dlatego w metodzie DeleteLink
wyszukaj jednostkę podstawową według parametru klucza , znajdź powiązaną jednostkę za pomocą parametru relatedKey , a następnie usuń skojarzenie. W zależności od modelu danych może być konieczne zaimplementowanie obu wersji programu DeleteLink
. Internetowy interfejs API wywoła poprawną wersję na podstawie identyfikatora URI żądania.