ASP.NET Core OData 实践——Lesson7通过Payload修改Reference(C#)

《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对多Navigation Property(C#)》《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对一Navigation Property(C#)》中,我们熟悉了在URI中指定Navigation Property的Key的方式修改Reference的方式。

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

这段代码主要逻辑

  1. 初始化输出参数
  • relatedKey = 0; 先将输出参数初始化为 0。
  1. 获取 OData 元数据和服务根地址
  • 通过 Request.GetRouteServices().GetService(typeof(IEdmModel)) 获取 EDM 模型。
  • 通过 Request.CreateODataLink() 获取服务根 URI。
  1. 空值校验
  • 如果 link 为 null,直接返回 false。
  1. 解析 OData 路径
  • 使用 ODataUriParser 解析传入的 URI(如 /odata/Employees(3)),得到 OData 路径对象。
  • ParsePath() 解析出路径段集合。
  1. 查找 KeySegment
  • 在路径段中查找最后一个 KeySegment,它包含主键信息。
  1. 提取主键值
  • 如果找不到 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 MethodRoute 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 MethodRoute 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 MethodRoute 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 MethodRoute 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

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值