Поделиться через


Действия и функции в OData версии 4 с использованием веб-API ASP.NET 2.2

Майк Уосон

В OData действия и функции — это способ добавления поведения на стороне сервера, которые сложно определить как операции CRUD для сущностей. В этом руководстве показано, как добавлять действия и функции в конечную точку OData версии 4 с помощью веб-API 2.2. В основе этого руководства — создание конечной точки OData версии 4 с помощью веб-API ASP.NET 2

Версии программного обеспечения, используемые в этом руководстве

  • Веб-API 2.2
  • OData v4
  • Visual Studio 2013 (скачайте Visual Studio 2017 здесь)
  • .NET 4.5

Версии учебников

Сведения об OData версии 3 см. в разделе OData Actions в веб-API ASP.NET 2.

Разница между действиями и функциями заключается в том, что действия могут иметь побочные эффекты, а функции — нет. Как действия, так и функции могут возвращать данные. Ниже приведены некоторые варианты использования действий.

  • Сложные транзакции.
  • Управление несколькими сущностями одновременно.
  • Разрешает обновлять только определенные свойства сущности.
  • Отправка данных, которые не являются сущностью.

Функции полезны для возврата сведений, которые не соответствуют непосредственно сущности или коллекции.

Действие (или функция) может быть нацелено на одну сущность или коллекцию. В терминологии OData это привязка. Вы также можете использовать "несвязанные" действия или функции, которые называются статическими операциями в службе.

Пример. Добавление действия

Давайте определим действие для определения значения продукта.

Примечание

В основе этого руководства — создание конечной точки OData версии 4 с помощью веб-API ASP.NET 2

Сначала добавьте ProductRating модель для представления оценок.

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

Кроме того, добавьте DbSet в ProductsContext класс , чтобы EF создаст таблицу Ratings в базе данных.

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

Добавление действия в EDM

В Файле WebApiConfig.cs добавьте следующий код:

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

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

Метод EntityTypeConfiguration.Action добавляет действие в модель данных сущности (EDM). Метод Parameter задает типизированный параметр для действия.

Этот код также задает пространство имен для EDM. Пространство имен имеет значение, так как универсальный код ресурса (URI) для действия включает полное имя действия:

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

Примечание

В типичной конфигурации IIS точка в этом URL-адресе приведет к возврату iis ошибки 404. Чтобы устранить эту проблему, добавьте следующий раздел в файл 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>

Добавление метода контроллера для действия

Чтобы включить действие "Ставка", добавьте следующий метод в 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);
}

Обратите внимание, что имя метода соответствует имени действия. Атрибут [HttpPost] указывает, что метод является методом HTTP POST.

Чтобы вызвать действие, клиент отправляет HTTP-запрос POST, как показано ниже:

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

{"Rating":5}

Действие Rate привязано к экземплярам Product, поэтому универсальный код ресурса (URI) действия — это полное имя действия, добавляемое к URI сущности. (Напомним, что для пространства имен EDM задано значение "ProductService", поэтому полное имя действия — "ProductService.Rate".)

Текст запроса содержит параметры действия в виде полезных данных JSON. Веб-API автоматически преобразует полезные данные JSON в объект ODataActionParameters , который является просто словарем значений параметров. Используйте этот словарь для доступа к параметрам в методе контроллера.

Если клиент отправляет параметры действия в неправильном формате, значение ModelState.IsValid равно false. Проверьте этот флаг в методе контроллера и верните ошибку, если isValid имеет значение false.

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

Пример. Добавление функции

Теперь добавим функцию OData, которая возвращает самый дорогой продукт. Как и ранее, первым шагом является добавление функции в EDM. В Файле WebApiConfig.cs добавьте следующий код.

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

В этом случае функция привязана к коллекции Products, а не к отдельным экземплярам Product. Клиенты вызывают функцию, отправляя запрос GET:

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

Ниже приведен метод контроллера для этой функции:

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

    // Other controller methods not shown.
}

Обратите внимание, что имя метода соответствует имени функции. Атрибут [HttpGet] указывает, что метод является методом HTTP GET.

Вот 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
}

Пример. Добавление несвязанной функции

В предыдущем примере была функция, привязанная к коллекции. В следующем примере мы создадим несвязанную функцию. Несвязанные функции вызываются как статические операции в службе. Функция в этом примере возвращает налог с продаж для заданного почтового индекса.

В файле WebApiConfig добавьте функцию в EDM:

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

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

Обратите внимание, что мы вызываем функцию непосредственно в ODataModelBuilder, а не в типе сущности или коллекции. Это сообщает построителю моделей, что функция является неограниченной.

Ниже приведен метод контроллера, реализующий функцию :

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

Не имеет значения, в какой контроллер веб-API вы размещаете этот метод. Вы можете поместить его в ProductsControllerили определить отдельный контроллер. Атрибут [ODataRoute] определяет шаблон URI для функции.

Ниже приведен пример клиентского запроса:

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

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
}