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 ProductsController
ou 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
}