Использование $select, $expand и $value в веб-API ASP.NET 2 OData
Майк Уассон
Обзор и примеры кода для параметров $expand, $select и $value в веб-API OData 2 для ASP.NET 4.x. Эти параметры позволяют клиенту управлять представлением, которое он получает с сервера.
- $expand приводит к включению связанных сущностей в ответ.
- $select выбирает подмножество свойств для включения в ответ.
- $value получает необработанное значение свойства.
Пример схемы
В этой статье мы будем использовать службу OData, которая определяет три сущности: Product, Supplier и Category. Каждый продукт имеет одну категорию и одного поставщика.
Ниже приведены классы C#, определяющие модели сущностей:
public class Supplier
{
[Key]
public string Key {get; set; }
public string Name { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
[ForeignKey("Supplier")]
public string SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
Обратите внимание, что Product
класс определяет свойства навигации Supplier
для и Category
. Класс Category
определяет свойство навигации для продуктов в каждой категории.
Чтобы создать конечную точку OData для этой схемы, используйте шаблон Visual Studio 2013, как описано в статье Создание конечной точки OData в веб-API ASP.NET. Добавьте отдельные контроллеры для продуктов, категорий и поставщиков.
Включение $expand и $select
В Visual Studio 2013 шаблон OData веб-API создает контроллер, который автоматически поддерживает $expand и $select. Для справки ниже приведены требования к поддержке $expand и $select в контроллере.
Для коллекций метод контроллера Get
должен возвращать IQueryable.
[Queryable]
public IQueryable<Category> GetCategories()
{
return db.Categories;
}
Для отдельных сущностей возвращается значение SingleResult<T>, где T — это объект IQueryable , содержащий ноль или одну сущность.
[Queryable]
public SingleResult<Category> GetCategory([FromODataUri] int key)
{
return SingleResult.Create(db.Categories.Where(c => c.ID == key));
}
Кроме того, декорируйте методы Get
атрибутом [Queryable] , как показано в предыдущих фрагментах кода. Кроме того, вызовите EnableQuerySupport для объекта HttpConfiguration при запуске. (Дополнительные сведения см. в разделе Включение параметров запроса OData.)
Использование $expand
При запросе сущности или коллекции OData ответ по умолчанию не включает связанные сущности. Например, ниже приведен ответ по умолчанию для набора сущностей Categories:
{
"odata.metadata":"http://localhost/odata/$metadata#Categories",
"value":[
{"ID":1,"Name":"Apparel"},
{"ID":2,"Name":"Toys"}
]
}
Как видите, в ответе не содержатся продукты, несмотря на то, что у сущности Категория есть ссылка навигации Продукты. Однако клиент может использовать $expand для получения списка продуктов для каждой категории. Параметр $expand входит в строку запроса запроса:
GET http://localhost/odata/Categories?$expand=Products
Теперь сервер будет включать продукты для каждой категории в соответствие с категориями. Ниже приведены полезные данные ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Categories",
"value":[
{
"Products":[
{"ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"},
{"ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"},
{"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"}
],
"ID":1,
"Name":"Apparel"
},
{
"Products":[
{"ID":4,"Name":"Yo-yo","Price":"4.95","CategoryId":2,"SupplierId":"WING"},
{"ID":5,"Name":"Puzzle","Price":"8.00","CategoryId":2,"SupplierId":"WING"}
],
"ID":2,
"Name":"Toys"
}
]
}
Обратите внимание, что каждая запись в массиве "значение" содержит список Products.
Параметр $expand принимает разделенный запятыми список свойств навигации для расширения. Следующий запрос расширяет категорию и поставщика для продукта.
GET http://localhost/odata/Products(1)?$expand=Category,Supplier
Ниже приведен текст ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Products/@Element",
"Category": {"ID":1,"Name":"Apparel"},
"Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
"ID":1,
"Name":"Hat",
"Price":"15.00",
"CategoryId":1,
"SupplierId":"CTSO"
}
Вы можете развернуть несколько уровней свойства навигации. В следующем примере представлены все продукты для категории, а также поставщик для каждого продукта.
GET http://localhost/odata/Categories(1)?$expand=Products/Supplier
Ниже приведен текст ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Categories/@Element",
"Products":[
{
"Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
"ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"
},
{
"Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
"ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"
},{
"Supplier":{
"Key":"FBRK","Name":"Fabrikam, Inc."
},"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"
}
],"ID":1,"Name":"Apparel"
}
По умолчанию веб-API ограничивает максимальную глубину расширения до 2. Это предотвращает отправку клиентом сложных запросов, таких как $expand=Orders/OrderDetails/Product/Supplier/Region
, которые могут оказаться неэффективными для запросов и создания больших ответов. Чтобы переопределить значение по умолчанию, задайте свойство MaxExpansionDepth в атрибуте [Queryable] .
[Queryable(MaxExpansionDepth=4)]
public IQueryable<Category> GetCategories()
{
return db.Categories;
}
Дополнительные сведения о параметре $expand см. в разделе Развертывание параметра системного запроса ($expand) в официальной документации по OData.
Использование $select
Параметр $select указывает подмножество свойств для включения в текст ответа. Например, чтобы получить только имя и цену каждого продукта, используйте следующий запрос:
GET http://localhost/odata/Products?$select=Price,Name
Ниже приведен текст ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Products&$select=Price,Name",
"value":[
{"Price":"15.00","Name":"Hat"},
{"Price":"12.00","Name":"Scarf"},
{"Price":"5.00","Name":"Socks"},
{"Price":"4.95","Name":"Yo-yo"},
{"Price":"8.00","Name":"Puzzle"}
]
}
Вы можете объединить $select и $expand в одном запросе. Обязательно включите развернутое свойство в параметр $select. Например, следующий запрос получает имя продукта и поставщика.
GET http://localhost/odata/Products?$select=Name,Supplier&$expand=Supplier
Ниже приведен текст ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Products&$select=Name,Supplier",
"value":[
{
"Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
"Name":"Hat"
},
{
"Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
"Name":"Scarf"
},
{
"Supplier":{"Key":"FBRK","Name":"Fabrikam, Inc."},
"Name":"Socks"
},
{
"Supplier":{"Key":"WING","Name":"Wingtip Toys"},
"Name":"Yo-yo"
},
{
"Supplier":{"Key":"WING","Name":"Wingtip Toys"},
"Name":"Puzzle"
}
]
}
Вы также можете выбрать свойства в развернутом свойстве. В следующем запросе разворачиваются продукты и выбирается название категории и название продукта.
GET http://localhost/odata/Categories?$expand=Products&$select=Name,Products/Name
Ниже приведен текст ответа:
{
"odata.metadata":"http://localhost/odata/$metadata#Categories&$select=Name,Products/Name",
"value":[
{
"Products":[ {"Name":"Hat"},{"Name":"Scarf"},{"Name":"Socks"} ],
"Name":"Apparel"
},
{
"Products":[ {"Name":"Yo-yo"},{"Name":"Puzzle"} ],
"Name":"Toys"
}
]
}
Дополнительные сведения о параметре $select см. в разделе Выбор параметра системного запроса ($select) в официальной документации по OData.
Получение отдельных свойств сущности ($value)
Клиент OData может получить отдельное свойство из сущности двумя способами. Клиент может получить значение в формате OData или получить необработанное значение свойства .
Следующий запрос получает свойство в формате OData.
GET http://localhost/odata/Products(1)/Name
Ниже приведен пример ответа в формате JSON:
HTTP/1.1 200 OK
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 90
{
"odata.metadata":"http://localhost:14239/odata/$metadata#Edm.String",
"value":"Hat"
}
Чтобы получить необработанное значение свойства, добавьте $value к URI:
GET http://localhost/odata/Products(1)/Name/$value
Вот ответ. Обратите внимание, что тип контента — text/plain, а не JSON.
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3
Hat
Для поддержки этих запросов в контроллере OData добавьте метод с именем GetProperty
, где Property
— это имя свойства . Например, метод для получения свойства Name будет называться GetName
. Метод должен возвращать значение этого свойства:
public async Task<IHttpActionResult> GetName(int key)
{
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
return Ok(product.Name);
}