Partilhar via


Ações e funções no OData v4 usando ASP.NET Web API 2.2

por Mike Wasson

No OData, ações e funções são uma maneira de adicionar comportamentos do lado do servidor que não são facilmente definidos como operações CRUD em entidades. Este tutorial mostra como adicionar ações e funções a um ponto de extremidade OData v4 usando a API Web 2.2. O tutorial se baseia no tutorial Criar um ponto de extremidade OData v4 usando ASP.NET Web API 2

Versões de software usadas no tutorial

  • API Web 2.2
  • OData v4
  • Visual Studio 2013 (baixe o Visual Studio 2017 aqui)
  • .NET 4.5

Versões do tutorial

Para o OData Versão 3, consulte OData Actions no ASP.NET Web API 2.

A diferença entre ações e funções é que as ações podem ter efeitos colaterais e as funções não. As ações e as funções podem retornar dados. Alguns usos para ações incluem:

  • Transações complexas.
  • Manipulando várias entidades ao mesmo tempo.
  • Permitindo atualizações apenas para determinadas propriedades de uma entidade.
  • Enviar dados que não são uma entidade.

As funções são úteis para retornar informações que não correspondem diretamente a uma entidade ou coleção.

Uma ação (ou função) pode direcionar uma única entidade ou uma coleção. Na terminologia OData, essa é a associação. Você também pode ter ações/funções "unbound", que são chamadas como operações estáticas no serviço.

Exemplo: adicionando uma ação

Vamos definir uma ação para classificar um produto.

Observação

Este tutorial se baseia no tutorial Criar um ponto de extremidade OData v4 usando ASP.NET Web API 2

Primeiro, adicione um ProductRating modelo para representar as classificações.

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; }  
    }
}

Adicione também um DbSet à classe , para que o ProductsContext EF crie uma tabela Classificações no banco de dados.

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; }
}

Adicionar a ação ao EDM

Em WebApiConfig.cs, adicione o seguinte código:

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

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

O método EntityTypeConfiguration.Action adiciona uma ação ao EDM (modelo de dados de entidade). O método Parameter especifica um parâmetro tipado para a ação.

Esse código também define o namespace para o EDM. O namespace é importante porque o URI da ação inclui o nome da ação totalmente qualificado:

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

Observação

Em uma configuração típica do IIS, o ponto nessa URL fará com que o IIS retorne o erro 404. Você pode resolve isso adicionando a seguinte seção ao arquivo 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>

Adicionar um método controller para a ação

Para habilitar a ação "Taxa", adicione o seguinte método 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);
}

Observe que o nome do método corresponde ao nome da ação. O atributo [HttpPost] especifica que o método é um método HTTP POST.

Para invocar a ação, o cliente envia uma solicitação HTTP POST como a seguinte:

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

{"Rating":5}

A ação "Taxa" está associada a instâncias do produto, portanto, o URI da ação é o nome de ação totalmente qualificado acrescentado ao URI da entidade. (Lembre-se de que definimos o namespace EDM como "ProductService", portanto, o nome da ação totalmente qualificado é "ProductService.Rate".)

O corpo da solicitação contém os parâmetros de ação como uma carga JSON. A API Web converte automaticamente o conteúdo JSON em um objeto ODataActionParameters , que é apenas um dicionário de valores de parâmetro. Use esse dicionário para acessar os parâmetros em seu método de controlador.

Se o cliente enviar os parâmetros de ação no formato errado, o valor de ModelState.IsValid será false. Verifique esse sinalizador no método do controlador e retorne um erro se IsValid for false.

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

Exemplo: adicionando uma função

Agora vamos adicionar uma função OData que retorna o produto mais caro. Como antes, a primeira etapa é adicionar a função ao EDM. Em WebApiConfig.cs, adicione o código a seguir.

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>();

Nesse caso, a função está associada à coleção Products, em vez de instâncias individuais do Produto. Os clientes invocam a função enviando uma solicitação GET:

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

Este é o método do controlador para esta função:

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

    // Other controller methods not shown.
}

Observe que o nome do método corresponde ao nome da função. O atributo [HttpGet] especifica que o método é um método HTTP GET.

Aqui está a resposta 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
}

Exemplo: adicionando uma função Unbound

O exemplo anterior era uma função associada a uma coleção. Neste próximo exemplo, criaremos uma função não relacionada . As funções não vinculádas são chamadas como operações estáticas no serviço. A função neste exemplo retornará o imposto sobre vendas de determinado código postal.

No arquivo WebApiConfig, adicione a função ao EDM:

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

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

Observe que estamos chamando Function diretamente no ODataModelBuilder, em vez do tipo de entidade ou coleção. Isso informa ao construtor de modelos que a função não está relacionada.

Este é o método do controlador que implementa a função :

[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);
}

Não importa em qual controlador de API Web você coloque esse método. Você pode colocá-lo em ProductsControllerou definir um controlador separado. O atributo [ODataRoute] define o modelo de URI para a função.

Aqui está um exemplo de solicitação de cliente:

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

A resposta 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
}