Creare un endpoint OData v4 usando API Web ASP.NET

Open Data Protocol (OData) è un protocollo di accesso ai dati per il Web. OData offre un modo uniforme per eseguire query e modificare set di dati tramite operazioni CRUD (creazione, lettura, aggiornamento ed eliminazione).

API Web ASP.NET supporta sia v3 che v4 del protocollo. È anche possibile avere un endpoint v4 che viene eseguito side-by-side con un endpoint v3.

Questa esercitazione illustra come creare un endpoint OData v4 che supporta le operazioni CRUD.

Versioni software usate nell'esercitazione

  • API Web 5.2
  • OData v4
  • Visual Studio 2017 (scaricare Visual Studio 2017 qui)
  • Entity Framework 6
  • .NET 4.7.2

Versioni dell'esercitazione

Per OData versione 3, vedere Creazione di un endpoint OData v3.

Creare il progetto di Visual Studio

In Visual Studio scegliere Nuovo>progetto dal menu File.

Espandere Installato>Visual C#>Web e selezionare il modello applicazione Web ASP.NET (.NET Framework). Assegnare al progetto il nome "ProductService".

Screenshot della finestra del nuovo progetto di Visual Studio, che mostra le opzioni di menu per creare un'applicazione Web A S P dot NET con il punto NET Framework.

Selezionare OK.

Screenshot dell'applicazione Web A S P dot NET, che mostra i modelli disponibili per creare l'applicazione con una cartella E P Web e un riferimento di base.

Selezionare il modello Vuoto. In Aggiungi cartelle e riferimenti principali per:, selezionare API Web. Selezionare OK.

Installare i pacchetti OData

Nel menu Strumenti selezionare Gestione pacchetti NuGet>Console di Gestione pacchetti. Nella finestra Console di Gestione pacchetti digitare:

Install-Package Microsoft.AspNet.Odata

Questo comando installa i pacchetti NuGet OData più recenti.

Aggiungere una classe del modello

Un modello è un oggetto che rappresenta un'entità dati nell'applicazione.

In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Modelli. Scegliere Aggiungi>classe dal menu di scelta rapida.

Screenshot della finestra esplora soluzioni, che evidenzia il percorso per aggiungere un oggetto classe modello al progetto.

Nota

Per convenzione, le classi di modello vengono inserite nella cartella Models, ma non è necessario seguire questa convenzione nei propri progetti.

Denominare la classe Product. Nel file Product.cs sostituire il codice boilerplate con il codice seguente:

namespace ProductService.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

La Id proprietà è la chiave di entità. I client possono eseguire query sulle entità in base alla chiave. Ad esempio, per ottenere il prodotto con ID 5, l'URI è /Products(5). La Id proprietà sarà anche la chiave primaria nel database back-end.

Abilitare Entity Framework

Per questa esercitazione si userà Entity Framework (EF) Code First per creare il database back-end.

Nota

L'API Web OData non richiede Entity Framework. Usare qualsiasi livello di accesso ai dati in grado di convertire le entità di database in modelli.

Installare prima di tutto il pacchetto NuGet per ENTITY Framework. Nel menu Strumenti selezionare Gestione pacchetti NuGet>Console di Gestione pacchetti. Nella finestra Console di Gestione pacchetti digitare:

Install-Package EntityFramework

Aprire il file Web.config e aggiungere la sezione seguente all'interno dell'elemento di configurazione , dopo l'elemento configSections .

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Questa impostazione aggiunge una stringa di connessione per un database LocalDB. Questo database verrà usato quando si esegue l'app in locale.

Aggiungere quindi una classe denominata ProductsContext alla cartella Models:

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

Nel costruttore assegna "name=ProductsContext" il nome della stringa di connessione.

Configurare l'endpoint OData

Aprire il file App_Start/WebApiConfig.cs. Aggiungere le istruzioni using seguenti:

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

Aggiungere quindi il codice seguente al metodo Register :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

Questo codice esegue due operazioni:

  • Crea un modello EDM (Entity Data Model).
  • Aggiunge una route.

Edm è un modello astratto dei dati. Il modello EDM viene usato per creare il documento di metadati del servizio. La classe ODataConventionModelBuilder crea un EDM usando le convenzioni di denominazione predefinite. Questo approccio richiede il minor codice. Se si vuole un maggiore controllo sull'EDM, è possibile usare la classe ODataModelBuilder per creare EDM aggiungendo proprietà, chiavi e proprietà di navigazione in modo esplicito.

Una route indica all'API Web come instradare le richieste HTTP all'endpoint. Per creare una route OData v4, chiamare il metodo di estensione MapODataServiceRoute .

Se l'applicazione ha più endpoint OData, creare una route separata per ognuna. Assegnare a ogni route un nome di route e un prefisso univoci.

Aggiungere il controller OData

Un controller è una classe che gestisce le richieste HTTP. Si crea un controller separato per ogni set di entità nel servizio OData. In questa esercitazione verrà creato un controller per l'entità Product .

In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Controllers e scegliere Aggiungi>classe. Denominare la classe ProductsController.

Nota

La versione di questa esercitazione per OData v3 usa lo scaffolding Aggiungi controller . Attualmente non esiste alcun scaffolding per OData v4.

Sostituire il codice boilerplate in ProductsController.cs con il codice seguente.

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Il controller usa la ProductsContext classe per accedere al database tramite ENTITY Framework. Si noti che il controller esegue l'override del metodo Dispose per eliminare ProductsContext.

Questo è il punto di partenza per il controller. Verranno quindi aggiunti metodi per tutte le operazioni CRUD.

Eseguire una query sul set di entità

Aggiungere i metodi seguenti a ProductsController.

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

La versione senza parametri del Get metodo restituisce l'intera raccolta Products. Il Get metodo con un parametro chiave cerca un prodotto in base alla chiave ,in questo caso la Id proprietà .

L'attributo [EnableQuery] consente ai client di modificare la query usando opzioni di query come $filter, $sort e $page. Per altre informazioni, vedere Supporto delle opzioni di query OData.

Aggiungere un'entità al set di entità

Per consentire ai client di aggiungere un nuovo prodotto al database, aggiungere il metodo seguente a ProductsController.

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

Aggiornare un'entità

OData supporta due semantiche diverse per l'aggiornamento di un'entità, PATCH e PUT.

  • PATCH esegue un aggiornamento parziale. Il client specifica solo le proprietà da aggiornare.
  • PUT sostituisce l'intera entità.

Lo svantaggio di PUT è che il client deve inviare valori per tutte le proprietà nell'entità, inclusi i valori che non cambiano. La specifica OData indica che patch è preferibile.

In ogni caso, ecco il codice per i metodi PATCH e PUT:

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

Nel caso di PATCH, il controller usa il tipo Delta<T> per tenere traccia delle modifiche.

Eliminare un'entità

Per consentire ai client di eliminare un prodotto dal database, aggiungere il metodo seguente a ProductsController.

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}