[转]ASP.NET web API 2 OData enhancements
本文转自:https://www.pluralsight.com/blog/tutorials/asp-net-web-api-2-odata-enhancements
Along with the release of Visual Studio 2013 came a new release of ASP.NET MVC (called MVC 5) and ASP.NET Web API (called Web API 2). Part of the enhancements to the Web API stack include some much needed features for OData that were missing in the initial release of the ASP.NET Web API OData that came out in June 2013. There were four new OData capabilities added in this release: $select, $expand, $value, and batch updates.
A quick review of the basics
OData is an open standard specification for exposing and consuming CRUD-centric (Create-Read-Update-Delete) data services over HTTP. OData follows most of the design guidelines of the REST architectural style and makes it very easy to implement data services or consume them on just about any modern development platform. OData has two main parts to it: the OData URL query syntax that allows you to perform rich queries from the client including filtering, sorting, and paging; and the full OData communication protocol that includes OData message formatting standards and the supported operations and calling patterns for performing CRUD operations via OData.
The first release of Web API OData added support for OData services using ASP.NET Web API. This included support for the OData query syntax for any Web API service that exposes IQueryable collection results via GET operations, as well as implementing an OData protocol compliant service that supports full CRUD operations on an object model with relationships between the entities. Let's focus on services that are “full” OData services with Web API.
The first step in using OData within your ASP.NET Web API site is to pull in the ASP.NET Web API 2 OData NuGet package.
When defining a Web API OData service that supports CRUD operations, you will generally want to inherit from the EntitySetController<EntityType,KeyType> base class. The base class exposes virtual methods for you to override for each of the CRUD operations mapping to the POST (Create), GET (Retrieve), PUT/PATCH (Update), and DELETE (Delete) HTTP verbs. For Create and Update operations, the base class handles the request and dispatches the call to a virtual method that lets you just deal with the entity being updated or created instead of handling the raw request yourself.
A simple controller dealing with Customers in a database for a Pizza ordering domain (Zza) is shown below.
public class CustomersController : EntitySetController<Customer, Guid>
{
ZzaDbContext _Context = new ZzaDbContext(); [Queryable()]
public override IQueryable Get()
{
return _Context.Customers;
} protected override Customer GetEntityByKey(Guid key)
{
return _Context.Customers.FirstOrDefault(c => c.Id == key);
} protected override Guid GetKey(Customer entity)
{
return entity.Id;
} protected override Customer CreateEntity(Customer entity)
{
if (_Context.Customers.Any(c=>c.Id == entity.Id))
throw ErrorsHelper.GetForbiddenError(Request,
string.Format("Customer with Id {0} already exists", entity.Id));
_Context.Customers.Add(entity);
_Context.SaveChanges();
return entity;
} protected override Customer UpdateEntity(Guid key, Customer update)
{
if (!_Context.Customers.Any(c => c.Id == key))
throw ErrorsHelper.GetResourceNotFoundError(Request);
update.Id = key;
_Context.Customers.Attach(update);
_Context.Entry(update).State = System.Data.Entity.EntityState.Modified;
_Context.SaveChanges();
return update;
} protected override Customer PatchEntity(Guid key, Delta patch)
{
var customer = _Context.Customers.FirstOrDefault(c => c.Id == key);
if (customer == null)
throw ErrorsHelper.GetResourceNotFoundError(Request);
patch.Patch(customer);
_Context.SaveChanges();
return customer;
} public override void Delete(Guid key)
{
var customer = _Context.Customers.FirstOrDefault(c => c.Id == key);
if (customer == null)
throw ErrorsHelper.GetResourceNotFoundError(Request);
_Context.Customers.Remove(customer);
_Context.SaveChanges();
} protected override void Dispose(bool disposing)
{
_Context.Dispose();
base.Dispose(disposing);
}
}
Hand in hand with this, you need to set up your routing for the ASP.NET site to support OData requests and to map them to an Entity Data Model that the OData support can use to expose the metadata about the server data model.
The following code from the WebApiConfig class in the App_Start folder of the ASP.NET project takes care of setting up that routing for this example:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapODataRoute("ZzaOData", "zza", GetEdmModel());
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet("Customers");
builder.EntitySet("Orders");
builder.EntitySet("OrderItems");
builder.EntitySet("Products");
return builder.GetEdmModel();
}
}
With that code in place in an ASP.NET site, you can start issuing OData query requests such as the following to get all customers in the state of Virginia ordered by LastName:
GET /zza/Customers()?$filter=State%20eq%20'VA'&$orderby=LastName
The payload of the HTTP response would contain the results, formatted using the JSON Light format of the OData specification. OData supports three formats for the payloads – JSON Light, JSON Verbose, and ATOM/XML. The JSON Light format is the default for Web API OData and just contains the basic entities and their properties/values. JSON Verbose is the original JSON format supported by OData version 1.0, but as its current name implies, it is a much larger payload because it contains metadata about the entities as well as hypermedia links for related objects and actions available on the entities. The XML format supported is the ATOM Publishing Protocol, which contains the entities and their properties and values as well as the metadata and hypermedia links.
To Create (insert) a new Customer, you would send an HTTP POST request to /zza/Customers with the Customer entity in the HTTP body using one of the supported formats. To perform an update, you can choose between an HTTP PUT, where you are expected to send the whole entity with all of its properties in the request body, and those will be used to update the entire entity in the persistence store on the back end. For partial updates, you can use the PATCH or MERGE verbs in your request and just send the properties that have changes, and those will be merged with the other existing properties on the back end to perform the update. For a DELETE, you issue the request to /zza/Customers/<id>, where <id> is the primary key identifier for the entity you are deleting.
Of course, if you are writing a .NET client for the OData service, you don’t have to worry about the low level HTTP requests that need to be issued, you can use the WCF Data Services client. This can confuse some people because of the fact that you are not doing anything to do with WCF on the back end if you use Web API OData. But the WCF Data Services Client is decoupled from the server side implementation by the OData wire level protocol – as long as the server is speaking OData, it doesn’t matter what platform it is implemented on. It might not even be Windows, but you can use the WCF Data Services Client to talk to it.
To use the WCF Data Services Client you simply go into the client project in Visual Studio, add the WCF Data Services Client NuGet package, and then invoke the Add Service Reference menu from Solution Explorer on the project. From there you put in the base address for the service (i.e. http://localhost:12345/zza) and it will retrieve the metadata about the service, the exposed entity collections, and the type information about the entity model from the server. It will generate a proxy class called Container for you and then you can write client code like the following to retrieve entities:
DataServiceCollection customers = new DataServiceCollection();
var odataProxy = new Container(new Uri("http://localhost.fiddler:39544/zza"));
IQueryable query =
from c in odataProxy.Customers where c.State == "VA" select c;
customers.Load(query);
customersDataGrid.ItemsSource = customers.ToList();
The DataServiceCollection will track changes made to the entities, you can add new entities to the collection, then call SaveChanges on the proxy. If you need to delete an entity, you do that with a call to Delete() on the proxy collection itself. You can make multiple changes on the client side and they are all cached in memory by the WCF Data Services Client proxy. When you call SaveChanges() on the proxy, it will send each of the changes down to the server at that point for persistence.
Web API 2 OData changes
As mentioned at the beginning of this post, Web API 2 added four new capabilities to the OData stack in ASP.NET: $select, $expand, $value, and batch update operations. The really cool thing is that for the first three, if you have already been using Web API OData since it was released in June 2012, you don’t need to make any changes to your code. Those new features just “light up” when you add the ASP.NET Web API 2 OData NuGet package as an update to the original one. For the batch update operations, it is just a simple change to your routing initialization and you are off and running with that capability. Let’s take a look at each one briefly.
Retrieving partial entities with $select
The $select operator lets you retrieve a “partial” entity in an OData query. This means you can request just the properties on a given entity type that you care about as a client at a particular point in time. The entity type may contain 50 properties in the server object model, but maybe you just need a few of those to present in a data grid on the client for example. You can issue a $select query that specifies the property names you care about like so:
GET /zza/Customers?$select=FirstName,LastName,Phone
If you are using the WCF Data Services Client in a .NET client application, you could just use the Select LINQ method on your query against the proxy:
var selectQuery = _OdataProxy.Customers.Select(c =>
new { FirstName = c.FirstName, LastName = c.LastName, Phone = c.Phone });
If you inspect the payload that comes back with a tool like Fiddler, you will see that each entity only contains the requested properties. If the underlying provider on the server side that executes the query is a LINQ provider like Entity Framework, it will execute a SELECT statement at the database level that only requests the specific properties. This will improve performance because the database does not need to return unnecessary data to the web server, and it saves bandwidth between the web server and the client application to only transmit what is needed.
Eager loading with $expand
The next new operator is the $expand operator. This lets you request related entities to the root collection you are querying. For example, if I am querying Customers, but I also want to retrieve the Orders for each Customer, as well as possibly the OrderItems for each Order, and maybe even the related Product for each OrderItem. The $expand operator lets you specify this easily as part of your query. If I just wanted Orders, the query would look like this:
GET /zza/Customers?$expand=Orders
If I wanted to go deeper and get OrderItems, you use a / separator for each level in the hierarchy:
GET /zza/Customers?$expand=Orders/OrderItems
The arguments to the expand operator need to be the property name exposed on the entity type for the related object or collection. So to go one level deeper to the Product for each OrderItem, it would look like:
GET /zza/Customers?$expand=Orders/OrderItems/Product
However, if you tried to issue that query against an existing Web API CustomersController like the one showed earlier, you would get a 400 status code response with the following error:
The request includes a $expand path which is too deep. The maximum depth allowed is 2. To increase the limit, set the 'MaxExpansionDepth' property on QueryableAttribute or ODataValidationSettings.
I wish more error messages were like this: tell me what is wrong and what to do about it. Basically they built in protection against clients being too greedy and asking to expand massive amounts of data in a single request. Simply adding a MaxExpansionDepth to the Queryable attribute on the Get method fixes this:
[Queryable(MaxExpansionDepth=3)]
public override IQueryable Get()
{
return _Context.Customers;
}
For the .NET clients, WCF Data Services adds an Expand() extension method that lets you express the $expand query directly on the collection exposed from your WCF Data Services Client:
var expandQuery = _OdataProxy.Customers.Expand("Orders/OrderItems/Product");
Getting a single property value
Part of the OData query syntax includes being able to retrieve a single property value on some object with a query that looks like this:
GET /zza/Products(1)/Name
Out of the box, ASP.NET Web API OData does not handle requests with this format. However, all you need to do is add a method to the respective Controller (ProductsController in this case) for each property that you want to expose in this way:
public string GetName(int key)
{
var product = _Context.Products.FirstOrDefault(p => p.Id == key);
if (product == null)
throw ErrorsHelper.GetResourceNotFoundError(Request);
return product.Name;
}
If you issue that request, you get back the property value in the payload formatted like this in JSON Light:
HTTP/1.1 200 OK
...
Content-Length: 97
{
"odata.metadata":"http://localhost:39544/zza/$metadata#Edm.String",
"value":"Plain Cheese"
}
However, sometimes you may just want just the value and not the surrounding structure. That is where the $value operator comes in that is now supported in Web API 2. Simply append /$value on the end of this query:
GET /zza/Products(1)/Name/$value Have the same method implemented to handle the call, and now the response payload will contain just the value returned with nothing else.
HTTP/1.1 200 OK
...
Content-Length: 12 Plain Cheese
Supporting batch updates
The initial release of Web API OData supported CUD operations (Create, Update, Delete), but only if you issued a separate HTTP request for each entity with the appropriate verb (POST, PUT or PATCH, and DELETE respectively). If you have a rich client that allows multiple modifications to be made and cached on the client side, it is very inefficient to make a separate round trip for each entity that has modifications in terms of the time it takes to negotiate each request and its associated connection. The OData protocol supports sending a collection of changes in a construct called a change set. This is basically a multipart request message that contains a collection of individual request messages in its body. The server side is responsible for pulling out the individual requests from the message and executing them one at a time. Web API 2 added support for handling multipart messages in general, and thus can also use this for handling the change sets of OData.
When you send down a change set in OData, you are supposed to send it to the root collection address (i.e. /zza/Customers) with /$batch appended as an HTTP POST request. This instructs the server that the HTTP body should contain a changeset to parse and process for updates.
The default OData routing I showed earlier will throw an error if a request like this comes in. All you need to do in Web API 2 to start handling change sets is to add a batch handler to the routing:
public static void Register(HttpConfiguration config)
{
config.Routes.MapODataRoute("ZzaOData", "zza", GetEdmModel(),
batchHandler: new DefaultODataBatchHandler(
GlobalConfiguration.DefaultServer));
}
Now you will be off and running handling OData change sets. If you are using the WCF Data Services Client from a .NET client, it issues individual requests per entity by default, but all you have to do to get it to switch to issuing a batch request when you call SaveChanges it pass it a parameter instructing it to do that:
var response = _OdataProxy.SaveChanges(SaveChangesOptions.Batch);
Now if you have made edits to multiple entities, added new ones, and deleted other ones on the client side, all those changes will be sent to the server in a single request.
So that wraps up our tour of what’s new for Web API OData in the latest release that came out with Visual Studio 2013 and .NET 4.5.1. All of these capabilities, including Web API 2 itself, all get added to your project through NuGet packages. So you don’t have to have .NET 4.5.1 to take advantage of them, they are backward compatible to .NET 4.0. If you add the ASP.NET Web API 2 OData NuGet package, it will make sure it has the Web API 2 NuGet packages installed as well.
As demonstrated, once you add the NuGet, $select and $expand operators just start working with no changes to your code. To get a single property value, you first add an accessor method for that property to the controller that takes in the entity key and has a naming convention of Get<PropertyName>(), returning the value. The OData routing will take care of dispatching calls to that method if they come in with the right addressing scheme. Now adding $value to the end of it gets you the raw value of the property in the response body. Finally, all you need to start supporting batch updates is to add a batchHandler parameter to your OData route, and take advantage of the built-in implementation DefaultODataBatchHandler.
You can download the complete sample code for this article here. If you want to dive deeper, check out Brian's courses Building ASP.NET Web API OData Services and Building Data-Centric Single Page Applications with Breeze.
[转]ASP.NET web API 2 OData enhancements的更多相关文章
- ASP.NET Web API基于OData的增删改查,以及处理实体间关系
本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先是比较典型的一对多关系,Supplier和Product. public class Product { ...
- [转]ASP.NET Web API基于OData的增删改查,以及处理实体间关系
本文转自:http://www.cnblogs.com/darrenji/p/4926334.html 本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先 ...
- [转]ASP.NET Web API对OData的支持
http://www.cnblogs.com/shanyou/archive/2013/06/11/3131583.html 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的 ...
- [转]Using $select, $expand, and $value in ASP.NET Web API 2 OData
本文转自:https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/using- ...
- ASP.NET Web API 2 OData v4教程
程序数据库格式标准化的开源数据协议 为了增强各种网页应用程序之间的数据兼容性,微软公司启动了一项旨在推广网页程序数据库格式标准化的开源数据协议(OData)计划,于此同时,他们还发 布了一款适用于OD ...
- 对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解
依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调 ...
- 在ASP.NET Web API中使用OData的Action和Function
本篇体验OData的Action和Function功能.上下文信息参考"ASP.NET Web API基于OData的增删改查,以及处理实体间关系".在本文之前,我存在的疑惑包括: ...
- Professional C# 6 and .NET Core 1.0 - Chapter 42 ASP.NET Web API
本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处: -------------------------------------------------------- ...
- ASP.NET Web API系列教程目录
ASP.NET Web API系列教程目录 Introduction:What's This New Web API?引子:新的Web API是什么? Chapter 1: Getting Start ...
随机推荐
- Linux下.Net Core+Nginx环境搭建小白教程
前言 对于接触.Net Core的我们来说之前从未接触过Linux,出于资源和性能及成本的考虑我们可能要将我们的环境搬到Linux下,这对于我们从未接触过Linux的童鞋们来说很棘手,那么我今天将带你 ...
- hdu X问题 (中国剩余定理不互质)
http://acm.hdu.edu.cn/showproblem.php?pid=1573 X问题 Time Limit: 1000/1000 MS (Java/Others) Memory ...
- UWP开发入门(五)——自定义Panel
各位好,终于讲到自定义Panel了.当系统自带的几个Panel比如Gird,StackPanel,RelativePanel不能满足我们的特定要求时(其实不常见啦),自定义Panel就显得非常必要,而 ...
- iOS 设置 (plist)
Architectures:
- “全栈2019”Java异常第十八章:Exception详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- [XHR]——重新认识 XMLHttpRequest
细说XMLHttpRequest如何使用 先来看一段使用XMLHttpRequest发送Ajax请求的简单示例代码. function sendAjax() { //构造表单数据 var formDa ...
- linux进程查看及管理的工具
介绍Linux进程查看及管理的工具:pstree, ps, pidof, pgrep, top, htop, glance, pmap, vmstat, dstat, kill, pkill, jo ...
- CentOS7系统安装 Maria Db(MYSQL)教程
一.背景Maria Db是流行的跨平台MySQL数据库管理系统的分支,被认为是MySQL 的完全替代品.Maria Db是由Sun在Sun Micro systems合并期间被Oracle收购后,于2 ...
- asp.net c# 虾米音乐API
最近用到虾米音乐的功能,主要是做一个分享音乐功能,找到好多代码,但是比较杂,有用的很少,因 此在此记录下,方便以后自己使用. 对于第三方网站,只要获取了唯一标识,基本上能抓取一些信息. 虾米 音乐的I ...
- 把 Reative Native 47 版本集成到已有的 Native iOS 工程中
一.搭建开发环境 http://reactnative.cn/docs/0.46/getting-started.html#content 二.创建一个模板 运行以下命令,创建一个最新版本的 reac ...