ASP.NET Core OData 9 正式釋出

yswenli發表於2024-10-09

我們很高興地宣佈,ASP.NET Core OData 9 已正式釋出,並在 NuGet 上提供:

  • Microsoft.AspNetCore.OData 9.0.0

此版本的主要亮點是將 OData .NET 依賴項更新到 8.x 主版本。 透過更新依賴項,我們能夠利用 Microsoft.OData.Core 8.xMicrosoft.OData.Edm 8.x 版本中引入的改進和新功能。

ASP.NET Core OData 9 版本將僅支援 .NET 8 或更高版本。

OData .NET 8 官方釋出公告解決了該版本中引入的主要更改。建議閱讀這篇文章以熟悉這些變化。

在本文中,我們將探討其中一些更改如何影響 ASP.NET Core OData 庫,以及如何在可能的情況下切換舊行為。

請求和響應負載中的字元編碼

在 OData .NET 8 中,我們引入了一個新的 JSON 編寫器,它在後臺使用 .NET Utf8JsonWriter 來編寫請求和響應有效負載。新的 JSON 編寫器速度明顯更快,並且是 ASP.NET Core OData 9 中的預設編寫器。

您可能會觀察到輸出負載上的字元編碼存在差異。我們將在以下各節中介紹這些差異。

編碼的字元子集

在其預設配置中,新的 JSON 編寫器不會像舊版 (OData .NET 7) 那樣對大量字元進行編碼。

預設情況下,舊版 JSON 編寫器對所有整數值小於 32 且大於 127 的字元進行編碼,基本上都是非 ASCII 字元。這計算出大約 65440 個字元!可以透過將 option 傳遞給預設的 JSON 編寫器工廠建構函式來覆蓋舊版 JSON 編寫器的預設配置。使用這種替代配置,將對大大減少的字元子集進行編碼。ODataStringEscapeOption.EscapeOnlyControls

新的 JSON 編寫器使用預設情況下配置了 encoder 選項的基礎。編碼的結果字符集要小得多。Utf8JsonWriterJavaScriptEncoder.UnsafeRelaxedJsonEscaping

下面是一個插圖 – 以 OData 服務的程式碼示例形式,演示了使用新的 JSON 編寫器時輸出有效負載的外觀:

// Model
namespace Ver900Sample.Models
{
    public class Order
    {
        public int Id { get; set; }
        public decimal Amount { get; set; }
        public string Note { get; set; }
    }
}

// Controller
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Ver900Sample.Models;

namespace Ver900Sample.Controllers
{
    public class OrdersController : ODataController
    {
        private static readonly List<Order> orders = new List<Order>
        {
            new Order { Id = 1, Amount = 130m, Note = "a - z, α - Ω" },
            new Order { Id = 2, Amount = 170.50m, Note = "😀 🐂 🐕" }
        };

        [EnableQuery]
        public SingleResult<Order> Get(int key)
        {
            return SingleResult.Create(orders.AsQueryable().Where(d => d.Id == key));
        }
    }
}

// Service configuration
using Ver900Sample.Models;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;

var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Order>("Orders");

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        model: modelBuilder.GetEdmModel()));

var app = builder.Build();

app.UseRouting();
app.MapControllers();

app.Run();

您可以使用該工具查詢此服務的訂單 1 – 瀏覽器可能不是此圖的好選擇,因為它會智慧解碼編碼的字元。curl

curl http://localhost:5090/Orders(1)

注意:5090 是計算機分配的隨機埠,因計算機而異。

以下是您將觀察到的結果:

{
  "@odata.context": "http://localhost:5090/$metadata#Orders/$entity",
  "Id": 1,
  "Amount": 130,
  "Note": "a - z, α - Ω"
}

在上述情況下,新的 JSON 編寫器不會對 和非 ASCII 字元進行編碼。αΩ

如果在您的方案中保留舊版 JSON 編寫器的行為很重要,則可以透過使用依賴項注入將新的 JSON 編寫器替換為舊版 JSON 編寫器來實現此目的。您可以透過修改上述程式碼片段中的方法呼叫來執行此操作,如下所示:AddOData

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddScoped<Microsoft.OData.Json.IJsonWriterFactory>(
                sp => new Microsoft.OData.Json.ODataJsonWriterFactory());
        }));

注意:該類是以前在 OData .NET 7 中命名的類。我們認為名稱中包含“Default”會使歧義永久存在,因為它不是 OData .NET 8 中的預設 JSON 編寫器工廠。ODataJsonWriterFactoryDefaultJsonWriterFactory

如果您在上述更改後查詢訂單 1,響應有效負載將如下所示:

{
  "@odata.context": "http://localhost:5090/$metadata#Orders/$entity",
  "Id": 1,
  "Amount": 130,
  "Note": "a - z, \u03b1 - \u03a9"
}

在上述情況下,將對非 ASCII 字元進行編碼。

還支援更嚴格的 JavaScript 編碼器。您可以配置您的服務以使用該編碼器,如下所示:Utf8JsonWriter

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddScoped<Microsoft.OData.Json.IJsonWriterFactory>(
                sp => new Microsoft.OData.Json.ODataUtf8JsonWriterFactory(
                    System.Text.Encodings.Web.JavaScriptEncoder.Default));
        }));

如果您在上述更改後查詢訂單 1,響應有效負載將如下所示:

{
  "@odata.context": "http://localhost:5090/$metadata#Orders/$entity",
  "Id": 1,
  "Amount": 130,
  "Note": "a - z, \u03B1 - \u03A9"
}

上述響應有效負載看起來類似於舊版 JSON 編寫器的輸出。

但是,請務必注意,使用更嚴格的 JavaScript 編碼器配置的編碼器並不是舊版 JSON 編寫器在編碼字元方面的映象。Utf8JsonWriter

要關閉迴圈,下面介紹如何將服務配置為使用具有不太嚴格編碼選項的舊版 JSON 編寫器:

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddScoped<Microsoft.OData.Json.IJsonWriterFactory>(
                sp => new Microsoft.OData.Json.ODataJsonWriterFactory(
                    Microsoft.OData.Json.ODataStringEscapeOption.EscapeOnlyControls));
        }));

選擇配置選項是因為它是 ASP.NET Core 8.0 中配置的預設選項。JavaScriptEncoder.UnsafeRelaxedJsonEscaping

Unicode 碼位的大寫字母

新的 JSON 編寫器在編碼輸出中使用大寫字母,而舊版 JSON 編寫器使用小寫字母。這兩個輸出都是合法的,客戶端反序列化任何一種格式都應該沒有問題。

使用上一節中的示例服務並配置了新的 JSON 編寫器,您可以按如下方式查詢訂單 2:

curl http://localhost:5090/Orders(2)

以下是您將觀察到的結果:

{
  "@odata.context": "http://localhost:5090/$metadata#Orders/$entity",
  "Id": 2,
  "Amount": 170.50,
  "Note": "\uD83D\uDE00 \uD83D\uDC02 \uD83D\uDC15"
}

請注意,Unicode 字元的編碼值為大寫。

如果您在配置了舊版 JSON 編寫器的情況下查詢相同的順序 2,則響應有效負載如下所示:

{
  "@odata.context": "http://localhost:5090/$metadata#Orders/$entity",
  "Id": 2,
  "Amount": 170.50,
  "Note": "\ud83d\ude00 \ud83d\udc02 \ud83d\udc15"
}

如果所描述的字元編碼更改在您的方案中是一個交易破壞者,請將新的 JSON 編寫器替換為舊版 JSON 編寫器,如圖所示。

對依賴項注入的更改

我們進行了重大更改,以適應 OData .NET 8 中的依賴關係注入重構。我們擺脫了非標準的依賴注入工件(例如),轉而使用 .NET 框架依賴注入抽象。IContainerBuilder

具體而言,對 OData 核心庫中方法的更改使配置 、 和如下所示成為可能:AddODataDefaultServicesODataReaderSettingsODataMessageWriterSettingsODataUriParserSettings

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddDefaultODataServices(
                odataVersion: Microsoft.OData.ODataVersion.V4,
                configureReaderAction: (messageReaderSettings) =>
                {
                    // Relevant changes to the ODataMessageReaderSettings instance here
                },
                configureWriterAction: (messageWriterSettings) =>
                {
                    // Relevant changes to the ODataMessageWriterSettings instance here
                },
                configureUriParserAction: (uriParserSettings) =>
                {
                    // // Relevant changes to the ODataUriParserSettings instance here
                });
        }));

這為開發人員在操作這些特定設定時提供了更大的靈活性。

該引數還使注入任何所需的依賴項變得簡單明瞭:IServiceCollection

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddSingleton<Microsoft.OData.ODataPayloadValueConverter>(
                sp => new CustomODataPayloadValueConverter());
        }));

// ...

public class CustomODataPayloadValueConverter : Microsoft.OData.ODataPayloadValueConverter
{
    public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)
    {
        if (edmTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.DateTimeOffset)
        {
            var dateTimeOffset = (DateTimeOffset)value;
            return dateTimeOffset.ToString("yyyy-MM-dd'T'HH:mm:ss.fffffffzzz");
        }

        return base.ConvertToPayloadValue(value, edmTypeReference);
    }
}

向後相容性標誌

OData .NET 8 的一個主要目標是確保遷移到新版本的客戶能夠輸出與在 OData .NET 7 中相同的響應負載。在進行更改以更好地與 OData 標準保持一致的地方,新增了相容性標誌,以便能夠切換舊行為。

我們為與 OData 標準保持一致而進行的更改的一個示例是,在服務後設資料負載中編寫十進位制屬性的屬性,以及為空間屬性編寫屬性。ScaleSRID

如果您查詢上一節中示例服務的服務後設資料終端節點 (http://localhost:5090/$metadata),您將獲得以下有效負載:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="Ver900Sample.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityType Name="Order">
                <Key>
                    <PropertyRef Name="Id" />
                </Key>
                <Property Name="Id" Type="Edm.Int32" Nullable="false" />
                <Property Name="Amount" Type="Edm.Decimal" Nullable="false" Scale="variable" />
                <Property Name="Note" Type="Edm.String" Nullable="false" />
            </EntityType>
        </Schema>
        <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityContainer Name="Container">
                <EntitySet Name="Orders" EntityType="Ver900Sample.Models.Order" />
            </EntityContainer>
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

在 OData .NET 7 中,該屬性將寫為 – 帶有大寫的“V”。與 OData 標準的這種偏差在 OData .NET 8 中已修復。該屬性現在寫入 ,如上面的有效負載所示。ScaleScale="Variable"Scale Scale="variable"

如果在您的方案中維護 OData .NET 7 行為很重要,您可以透過切換功能標誌來實現這一點,如下所示:

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddDefaultODataServices(
                odataVersion: Microsoft.OData.ODataVersion.V4,
                configureReaderAction: null,
                configureWriterAction: (messageWriterSettings) =>
                {
                    messageWriterSettings.LibraryCompatibility |= Microsoft.OData.ODataLibraryCompatibility.UseLegacyVariableCasing;
                },
                configureUriParserAction: null);
        }));

切換標誌後,將寫入該屬性,以便與 OData .NET 7 相容。ODataLibraryCompability.UseLegacyVariableCasingScaleScale="Variable"

您可以根據需要組合任意數量的相容性標誌來實現所需的行為。

若要切換所有相容性標誌以實現 OData .NET 7 相容性,可以使用方便的標誌,如下所示:ODataLibraryCompatibility.Version7

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        routePrefix: string.Empty,
        model: modelBuilder.GetEdmModel(),
        configureServices: (services) =>
        {
            services.AddDefaultODataServices(
                odataVersion: Microsoft.OData.ODataVersion.V4,
                configureReaderAction: null,
                configureWriterAction: (messageWriterSettings) =>
                {
                    messageWriterSettings.LibraryCompatibility |= Microsoft.OData.ODataLibraryCompatibility.Version7;
                },
                configureUriParserAction: null);
        }));

結論

我們邀請您試用 ASP.NET Core OData 9 並與我們分享您的反饋。感謝您對 OData 生態系統的持續支援和貢獻。

相關文章