大纲
在《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对多Navigation Property(C#)》和《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对一Navigation Property(C#)》中,我们熟悉了在URI中指定Navigation Property的Key的方式修改Reference的方式。
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref | 向基类类型Entity的导航属性设置一个新的Entity |
POST | ~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref | 向派生类型Entity的导航属性设置一个新的Entity |
PUT | ~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref | 向基类类型Entity的导航属性设置一个新的Entity |
PUT | ~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref | 向派生类型Entity的导航属性设置一个新的Entity |
本文我们将讲解如何通过Payload修改Reference。
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
POST | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类类型Entity的导航属性设置一个新的Entity(Payload传递) |
主要模型设计
我们仍然借用在《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对多Navigation Property(C#)》和《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对一Navigation Property(C#)》的模型,只是做了少许的修改。
namespace Lesson7.Models
{
public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }
public List<Order> Orders { get; set; } = new List<Order>();
}
}
namespace Lesson7.Models
{
public class Employee
{
public int Id { get; set; }
public required string Name { get; set; }
}
}
namespace Lesson7.Models
{
public class EnterpriseCustomer : Customer
{
public List<Employee> RelationshipManagers { get; set; } = new List<Employee>();
}
}
namespace Lesson7.Models
{
public class Order
{
public int Id { get; set; }
public decimal Amount { get; set; }
public Customer? Customer { get; set; }
}
}
using System.IO;
namespace Lesson7.Models
{
public class ThirdpartyPaymentOrder : Order
{
public Customer? PaidByCustomer { get; set; } // 代付的客户
}
}
下面的例子,我们将通过修改Order的Customer、ThirdpartyPaymentOrder的PaidByCustomer、EnterpriseCustomer的RelationshipManagers 和 Customer的Orders来演示Payload的引用。
控制器设计
在《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对多Navigation Property(C#)》和《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对一Navigation Property(C#)》介绍的两个控制器的基础上,我们需要新增一些代码。
在这两个控制器中,我们都新增如下代码
private bool TryParseRelatedKey(Uri link, out int relatedKey)
{
relatedKey = 0;
var model = Request.GetRouteServices().GetService(typeof(IEdmModel)) as IEdmModel;
var serviceRoot = Request.CreateODataLink();
if (link == null)
{
return false;
}
var uriParser = new ODataUriParser(model, new Uri(serviceRoot), link);
// NOTE: ParsePath may throw exceptions for various reasons
ODataPath parsedODataPath = uriParser.ParsePath(); // Renamed variable to avoid conflict
KeySegment? keySegment = parsedODataPath.OfType<KeySegment>().LastOrDefault();
if (keySegment == null || !int.TryParse(keySegment.Keys.First().Value?.ToString(), out relatedKey))
{
return false;
}
return true;
}
这段代码主要逻辑
- 初始化输出参数
- relatedKey = 0; 先将输出参数初始化为 0。
- 获取 OData 元数据和服务根地址
- 通过 Request.GetRouteServices().GetService(typeof(IEdmModel)) 获取 EDM 模型。
- 通过 Request.CreateODataLink() 获取服务根 URI。
- 空值校验
- 如果 link 为 null,直接返回 false。
- 解析 OData 路径
- 使用 ODataUriParser 解析传入的 URI(如 /odata/Employees(3)),得到 OData 路径对象。
- ParsePath() 解析出路径段集合。
- 查找 KeySegment
- 在路径段中查找最后一个 KeySegment,它包含主键信息。
- 提取主键值
- 如果找不到 KeySegment 或主键无法转换为 int,则返回 false。
- 否则将主键值赋给 relatedKey 并返回 true。
设计这个方法的原因是,OData官方推荐的使用Payload的标准$ref路由方式是要在Payload中用"@odata.id"
指定资源信息,比如:
POST /odata/Orders(2)/Customer/$ref
Content-Type: application/json
{
"@odata.id": "/odata/Customers(1)"
}
而"@odata.id"
内容的解析就需要用到TryParseRelatedKey方法。
OrdersController
向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
public ActionResult CreateRefToCustomer([FromRoute] int key, [FromBody] Uri link)
{
var order = orders.SingleOrDefault(d => d.Id.Equals(key));
if (order == null)
{
return NotFound();
}
int relatedKey;
if (!TryParseRelatedKey(link, out relatedKey))
{
return BadRequest();
}
// Quick, lazy and dirty
order.Customer = new Customer { Id = relatedKey, Name = $"Customer {relatedKey}" };
return Created(order.Customer);
}
POST
- Request
curl --location 'http://localhost:5119/odata/Orders(1)/Customer/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Customers(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers/$entity",
"Id": 1,
"Name": "Customer 1"
}
PUT
- Request
curl --location --request PUT 'http://localhost:5119/odata/Orders(1)/Customer/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Customers(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers/$entity",
"Id": 1,
"Name": "Customer 1"
}
向派生类型Entity的导航属性设置一个新的Entity(Payload传递)
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类类型Entity的导航属性设置一个新的Entity(Payload传递) |
public ActionResult CreateRefToPaidByCustomerFromThirdpartyPaymentOrder([FromRoute] int key, [FromBody] Uri link)
{
var order = orders.OfType<ThirdpartyPaymentOrder>().SingleOrDefault(d => d.Id.Equals(key));
if (order == null)
{
return NotFound();
}
int relatedKey;
if (!TryParseRelatedKey(link, out relatedKey))
{
return BadRequest();
}
var customer = customers.SingleOrDefault(c => c.Id == relatedKey);
if (customer == null)
{
customer = new Customer { Id = relatedKey, Name = $"Customer {relatedKey}" };
customers.Add(customer);
}
// Quick, lazy and dirty
order.PaidByCustomer = customer;
return Created(order.PaidByCustomer);
}
POST
- Request
curl --location 'http://localhost:5119/odata/Orders(2)/Lesson7.Models.ThirdpartyPaymentOrder/PaidByCustomer/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Customers(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers/$entity",
"Id": 1,
"Name": "Customer 1"
}
PUT
- Request
curl --location --request PUT 'http://localhost:5119/odata/Orders(2)/Lesson7.Models.ThirdpartyPaymentOrder/PaidByCustomer/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Customers(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers/$entity",
"Id": 1,
"Name": "Customer 1"
}
CustomersController
向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{navigationproperty}/$ref | 向基类类型Entity的导航属性设置一个新的Entity(Payload传递) |
public ActionResult CreateRefToOrders([FromRoute] int key, [FromBody] Uri link)
{
if (!TryParseRelatedKey(link, out int relatedKey))
{
return BadRequest("Invalid related key.");
}
var customer = customers.SingleOrDefault(d => d.Id.Equals(key));
if (customer == null)
{
return NotFound();
}
var order = orders.SingleOrDefault(o => o.Id == relatedKey);
if (order == null)
{
order = new Order { Id = relatedKey, Amount = relatedKey * 10 };
orders.Add(order);
customer.Orders.Add(order);
return Ok(order);
}
if (!customer.Orders.Any(o => o.Id == relatedKey))
{
// Add the order to the customer's orders
customer.Orders.Add(order);
}
// Quick, lazy and dirty
customer.Orders.Add(order);
return Ok(order);
}
POST
- Request
curl --location 'http://localhost:5119/odata/Customers(1)/Orders/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Orders(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Orders/$entity",
"Id": 1,
"Amount": 80
}
PUT
- Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(1)/Orders/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Orders(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Orders/$entity",
"Id": 1,
"Amount": 80
}
向派生类型Entity的导航属性设置一个新的Entity(Payload传递)
Request Method | Route Template | 说明 |
---|---|---|
POST | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类型Entity的导航属性设置一个新的Entity(Payload传递) |
PUT | ~/{entityset}/{key}/{cast}/{navigationproperty}/$ref | 向派生类类型Entity的导航属性设置一个新的Entity(Payload传递) |
public ActionResult CreateRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key, [FromBody] Uri link)
{
if (!TryParseRelatedKey(link, out int relatedKey))
{
return BadRequest("Invalid related key.");
}
var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));
if (customer == null)
{
return NotFound();
}
var employee = employees.SingleOrDefault(e => e.Id == relatedKey);
if (employee == null)
{
employee = new Employee { Id = relatedKey, Name = $"Employee {relatedKey}" };
employees.Add(employee);
}
if (!customer.RelationshipManagers.Any(e => e.Id == relatedKey))
{
customer.RelationshipManagers.Add(employee);
}
return Ok(employee);
}
POST
- Request
curl --location 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Employees(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Employees/$entity",
"Id": 1,
"Name": "Employee 1"
}
PUT
- Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers/$ref' \
--header 'Content-Type: application/json' \
--data-raw '{
"@odata.id": "http://localhost:5119/odata/Employees(1)"
}'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Employees/$entity",
"Id": 1,
"Name": "Employee 1"
}
主程序
由于我们在修改EnterpriseCustomer的RelationshipManagers时,需要Employee的资源定位,所以需要在主程序中将Employee注册为一个EntitySet。
using Lesson7.Models;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using Microsoft.OData.Edm;
var builder = WebApplication.CreateBuilder(args);
// 提取 OData EDM 模型构建为方法,便于维护和扩展
static IEdmModel GetEdmModel()
{
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
modelBuilder.EntitySet<Order>("Orders");
modelBuilder.EntitySet<Employee>("Employees");
return modelBuilder.GetEdmModel();
}
// 添加 OData 服务和配置
builder.Services.AddControllers().AddOData(options =>
options.Select()
.Filter()
.OrderBy()
.Expand()
.Count()
.SetMaxTop(null)
.AddRouteComponents("odata", GetEdmModel())
);
var app = builder.Build();
app.UseRouting();
app.MapControllers();
app.Run();
代码地址
https://github.com/f304646673/odata/tree/main/csharp/Lesson/Lesson7