我們很高興地宣佈,ASP.NET Core OData 9 已正式釋出,並在 NuGet 上提供:
- Microsoft.AspNetCore.OData 9.0.0
此版本的主要亮點是將 OData .NET 依賴項更新到 8.x 主版本。 透過更新依賴項,我們能夠利用 Microsoft.OData.Core 8.x 和 Microsoft.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 選項的基礎。編碼的結果字符集要小得多。Utf8JsonWriter
JavaScriptEncoder.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 編寫器工廠。ODataJsonWriterFactory
DefaultJsonWriterFactory
如果您在上述更改後查詢訂單 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 核心庫中方法的更改使配置 、 和如下所示成為可能:AddODataDefaultServices
ODataReaderSettings
ODataMessageWriterSettings
ODataUriParserSettings
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 標準保持一致而進行的更改的一個示例是,在服務後設資料負載中編寫十進位制屬性的屬性,以及為空間屬性編寫屬性。Scale
SRID
如果您查詢上一節中示例服務的服務後設資料終端節點 (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 中已修復。該屬性現在寫入 ,如上面的有效負載所示。Scale
Scale="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.UseLegacyVariableCasing
Scale
Scale="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 生態系統的持續支援和貢獻。