Obsługa relacji jednostek w protokole OData V3 przy użyciu interfejsu Web API 2

według Jan Wasson

Pobierz ukończony projekt

Większość zestawów danych definiuje relacje między jednostkami: klienci mają zamówienia; książki mają autorów; produkty mają dostawców. Korzystając z protokołu OData, klienci mogą nawigować po relacjach jednostek. W danym produkcie można znaleźć dostawcę. Można również tworzyć i usuwać relacje. Na przykład można ustawić dostawcę dla produktu.

W tym samouczku pokazano, jak obsługiwać te operacje w interfejsie API sieci Web ASP.NET. Samouczek jest oparty na samouczku Tworzenie punktu końcowego OData V3 przy użyciu interfejsu Web API 2.

Wersje oprogramowania używane w samouczku

  • Internetowy interfejs API 2
  • Usługa OData w wersji 3
  • Entity Framework 6

Dodawanie jednostki dostawcy

Najpierw musimy dodać nowy typ jednostki do naszego ź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 przypadku, które mogą być mniej typowe niż użycie klucza liczb całkowitych. Jednak warto zobaczyć, jak usługa OData obsługuje inne typy kluczy niż liczby całkowite.

Następnie utworzymy relację poprzez dodanie właściwości Supplier do klasy Product:

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 nieogólnymi do klasy ProductServiceContext, tak aby Entity Framework obejmował 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 programie WebApiConfig.cs Dodaj jednostkę "dostawcy" do modelu modelu EDM:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");

Aby uzyskać dostawcę produktu, klient wysyła żądanie GET:

GET /Products(1)/Supplier

Tutaj "dostawca" jest właściwością nawigacji dla typu Product. W tym przypadku Supplier odnosi się do pojedynczego elementu, ale właściwość nawigacji może również zwracać kolekcję (relacja jeden-do-wielu lub wiele-do-wielu).

Aby obsłużyć to żądanie, Dodaj następującą metodę do klasy ProductsController:

// 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 pokrewną jednostkę—w tym przypadku wystąpienie Supplier. Nazwa metody i nazwa parametru są oba 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 jest zgodny z typem danych klucza nadrzędnego.

Ważne jest również uwzględnienie atrybutu [FromOdataUri] w parametrze klucza . Ten atrybut wskazuje, że interfejs API sieci Web ma używać reguł składni OData podczas analizowania klucza z żądania URI.

Usługa OData obsługuje tworzenie lub usuwanie relacji między dwoma jednostkami. W terminologii OData relacja jest "link". Każdy link ma identyfikator URI z jednostką/$Links/jednostką. Na przykład łącze od produktu do dostawcy wygląda następująco:

/Products(1)/$links/Supplier

Aby utworzyć nowe łącze, 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ć łącze "Product (1)" do "dostawca (" CTSO "), klient wysyła żądanie podobne do następujących:

POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50

{"url":"http://localhost/odata/Suppliers('CTSO')"}

Aby usunąć łącze, klient wysyła żądanie usunięcia do identyfikatora URI linku.

Tworzenie linków

Aby umożliwić klientowi tworzenie linków dostawcy produktu, Dodaj następujący kod do klasy ProductsController:

[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 (produkt)
  • 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 jednostki powiązanej. Ta wartość jest pobierana z treści żądania. Na przykład identyfikator URI linku może mieć wartość "http://localhost/odata/Suppliers('CTSO'), co oznacza dostawcę o IDENTYFIKATORze" CTSO ".

Metoda używa linku, aby wyszukać dostawcę. Jeśli zostanie znaleziony pasujący dostawca, Metoda ustawia właściwość Product.Supplier i zapisuje wynik do bazy danych.

Najtrudniejszą częścią jest analiza identyfikatora URI linku. Zasadniczo należy zasymulować wynik wysłania żądania GET do tego identyfikatora URI. W poniższej metodzie pomocnika pokazano, jak to zrobić. Metoda wywołuje proces routingu interfejsu API sieci Web i przywraca wystąpienie ODataPath reprezentujące przeanalizowane ścieżki OData. Dla 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 linków

Aby usunąć łącze, Dodaj następujący kod do klasy ProductsController:

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ą jednostką Supplier. Jeśli właściwość nawigacji jest kolekcją, identyfikator URI służący do usunięcia linku musi zawierać klucz 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 zwraca klucz jednostki powiązanej. Tak więc w metodzie DeleteLink należy odszukać jednostkę podstawową według parametru klucza , znaleźć powiązaną jednostkę przez parametr relatedKey , a następnie usunąć skojarzenie. W zależności od modelu danych może być konieczne zaimplementowanie obu wersji DeleteLink. Interfejs API sieci Web będzie wywoływał poprawną wersję na podstawie identyfikatora URI żądania.