Azioni e funzioni in OData v4 Usando API Web ASP.NET 2.2

di Mike Wasson

In OData le azioni e le funzioni sono un modo per aggiungere comportamenti lato server che non sono facilmente definiti come operazioni CRUD sulle entità. Questa esercitazione illustra come aggiungere azioni e funzioni a un endpoint OData v4 usando l'API Web 2.2. L'esercitazione si basa sull'esercitazione Creare un endpoint OData v4 Usando API Web ASP.NET 2

Versioni software usate nell'esercitazione

  • API Web 2.2
  • OData v4
  • Visual Studio 2013 (scaricare Visual Studio 2017 qui)
  • .NET 4.5

Versioni dell'esercitazione

Per OData versione 3, vedere Azioni OData in API Web ASP.NET 2.

La differenza tra azioni e funzioni è che le azioni possono avere effetti collaterali e funzioni non. Entrambe le azioni e le funzioni possono restituire dati. Alcuni usi per le azioni includono:

  • Transazioni complesse.
  • Modifica di diverse entità contemporaneamente.
  • Consentire gli aggiornamenti solo a determinate proprietà di un'entità.
  • Invio di dati che non sono un'entità.

Le funzioni sono utili per restituire informazioni che non corrispondono direttamente a un'entità o a una raccolta.

Un'azione (o una funzione) può essere destinazione di una singola entità o di una raccolta. Nella terminologia OData si tratta dell'associazione. È anche possibile avere azioni/funzioni "unbound", chiamate come operazioni statiche nel servizio.

Esempio: Aggiunta di un'azione

Definiamo un'azione per valutare un prodotto.

Nota

Questa esercitazione si basa sull'esercitazione Creare un endpoint OData v4 Usando API Web ASP.NET 2

Aggiungere innanzitutto un ProductRating modello per rappresentare le classificazioni.

namespace ProductService.Models
{
    public class ProductRating
    {
        public int ID { get; set; }
        public int Rating { get; set; }
        public int ProductID { get; set; }
        public virtual Product Product { get; set; }  
    }
}

Aggiungere anche un dbSet alla ProductsContext classe, in modo che EF creerà una tabella Ratings nel database.

public class ProductsContext : DbContext
{
    public ProductsContext() 
            : base("name=ProductsContext")
    {
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Supplier> Suppliers { get; set; }
    // New code:
    public DbSet<ProductRating> Ratings { get; set; }
}

Aggiungere l'azione all'EDM

In WebApiConfig.cs aggiungere il codice seguente:

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>()
    .Action("Rate")
    .Parameter<int>("Rating");

Il metodo EntityTypeConfiguration.Action aggiunge un'azione al modello di dati dell'entità (EDM). Il metodo Parameter specifica un parametro tipizzato per l'azione.

Questo codice imposta anche lo spazio dei nomi per EDM. Lo spazio dei nomi è importante perché l'URI per l'azione include il nome dell'azione completo:

http://localhost/Products(1)/ProductService.Rate

Nota

In una configurazione IIS tipica, il punto in questo URL causerà la restituzione dell'errore 404 da IIS. È possibile risolvere questo problema aggiungendo la sezione seguente al file di Web.Config:

<system.webServer>
    <handlers>
      <clear/>
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" 
          verb="*" type="System.Web.Handlers.TransferRequestHandler" 
          preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

Aggiungere un metodo Controller per l'azione

Per abilitare l'azione "Rate", aggiungere il metodo seguente a ProductsController:

[HttpPost]
public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    int rating = (int)parameters["Rating"];
    db.Ratings.Add(new ProductRating
    {
        ProductID = key,
        Rating = rating
    });

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateException e)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

Si noti che il nome del metodo corrisponde al nome dell'azione. L'attributo [HttpPost] specifica il metodo è un metodo HTTP POST.

Per richiamare l'azione, il client invia una richiesta HTTP POST simile alla seguente:

POST http://localhost/Products(1)/ProductService.Rate HTTP/1.1
Content-Type: application/json
Content-Length: 12

{"Rating":5}

L'azione "Rate" è associata alle istanze di Product, quindi l'URI per l'azione è il nome dell'azione completo aggiunto all'URI dell'entità. Si noti che lo spazio dei nomi EDM viene impostato su "ProductService", quindi il nome dell'azione completo è "ProductService.Rate".

Il corpo della richiesta contiene i parametri di azione come payload JSON. L'API Web converte automaticamente il payload JSON in un oggetto ODataActionParameters , che è solo un dizionario dei valori dei parametri. Usare questo dizionario per accedere ai parametri nel metodo controller.

Se il client invia i parametri di azione nel formato errato, il valore di ModelState.IsValid è false. Controllare questo flag nel metodo controller e restituire un errore se IsValid è false.

if (!ModelState.IsValid)
{
    return BadRequest();
}

Esempio: Aggiunta di una funzione

Aggiungere ora una funzione OData che restituisce il prodotto più costoso. Come prima, il primo passaggio aggiunge la funzione all'EDM. In WebApiConfig.cs aggiungere il codice seguente.

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntitySet<Supplier>("Suppliers");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>().Collection
    .Function("MostExpensive")
    .Returns<double>();

In questo caso, la funzione è associata alla raccolta Products, anziché alle singole istanze di Product. I client richiamano la funzione inviando una richiesta GET:

GET http://localhost:38479/Products/ProductService.MostExpensive

Ecco il metodo controller per questa funzione:

public class ProductsController : ODataController
{
    [HttpGet]
    public IHttpActionResult MostExpensive()
    {
        var product = db.Products.Max(x => x.Price);
        return Ok(product);
    }

    // Other controller methods not shown.
}

Si noti che il nome del metodo corrisponde al nome della funzione. L'attributo [HttpGet] specifica il metodo è un metodo HTTP GET.

Ecco la risposta HTTP:

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 00:44:07 GMT
Content-Length: 85

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Decimal","value":50.00
}

Esempio: Aggiunta di una funzione non in uscita

L'esempio precedente è una funzione associata a una raccolta. In questo esempio successivo verrà creata una funzione non in uscita . Le funzioni non in ingresso vengono chiamate operazioni statiche nel servizio. La funzione in questo esempio restituirà l'imposta sulle vendite per un determinato codice postale.

Nel file WebApiConfig aggiungere la funzione all'EDM:

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Function("GetSalesTaxRate")
    .Returns<double>()
    .Parameter<int>("PostalCode");

Si noti che la funzione viene chiamata direttamente in ODataModelBuilder anziché sul tipo di entità o sulla raccolta. In questo modo il generatore di modelli indica che la funzione non è in uscita.

Ecco il metodo controller che implementa la funzione:

[HttpGet]
[ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")]
public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode)
{
    double rate = 5.6;  // Use a fake number for the sample.
    return Ok(rate);
}

Non importa quale controller API Web si inserisce in questo metodo. È possibile inserirlo in ProductsControllero definire un controller separato. L'attributo [ODataRoute] definisce il modello URI per la funzione.

Ecco una richiesta client di esempio:

GET http://localhost:38479/GetSalesTaxRate(PostalCode=10) HTTP/1.1

La risposta HTTP:

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 01:05:32 GMT
Content-Length: 82

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Double","value":5.6
}