本文屬於OData系列html
目錄前端
- 武裝你的WEBAPI-OData入門
- 武裝你的WEBAPI-OData便捷查詢
- 武裝你的WEBAPI-OData分頁查詢
- 武裝你的WEBAPI-OData資源更新Delta
- 武裝你的WEBAPI-OData之EDM
- 武裝你的WEBAPI-OData使用Endpoint
- 武裝你的WEBAPI-OData常見問題
很是喜歡OData,在各類新項目中都使用了這個技術。對於.NET 5.0,OData
推出了8.0preview,因而就試用了一下。發現坑仍是很是多,若是不是頗有必要的話,建議仍是先等等。我使用的緣由是在.NET 5.0的狀況,7.x版本的OData會形成[Authorize]
沒法正常工做,致使權限認證沒法正常進行。git
運行環境以下:github
Microsoft.AspNetCore.OData.Versioning.ApiExplorer
這個庫不支持新版的OData
,因此版本控制只能使用OData 8.0.0自帶的路由方式控制。路由的形式有了變化,OData 8.0.0中,在Controller上標記了ODataRoutePrefix以後,不要標記無參數的ODataRoute。如今ODataRoute會從ODataRoutePrefix開始路由,若是標記無參數的ODataRoute,實際上至關於標記了兩次,則系統會認爲有兩個相同的方法,操做重複路由。對於一個有參數,一個無參數的,能夠給有參數的方法標記[ODataRoute("id")]。有一個例外,若是參數名稱是key
,那麼能夠不標記。web
注意,請不要直接使用[HttpGet("id")]的形式給OData指定路由,這個形式會直接忽略掉OData直接從/
開始路由。json
其實我也以爲新的方式纔是更合理的。c#
我推測應該是bug,在Controller方法只有一個Get而且明確標誌了[HttpGet]的形式,依然提示錯誤。這個問題能夠參考這裏。api
services.AddSwaggerGen(options => { // ........................ options.DocInclusionPredicate((name, api) => api.HttpMethod != null); });
這個我估摸也是bug,請注意,必須將services.AddOData放在services.AddControllers以前,不然在Controller中沒法識別ODataOutputFormatter,而後參考這裏解決問題。app
services.AddControllers( options => { foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0)) { outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata")); } foreach (var inputFormatter in options.InputFormatters.OfType<ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0)) { inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata")); } });
如今EdmBuilder不能識[Key]來進行主鍵的標記了,須要顯式添加HasKey
:webapp
var configuration = builder.EntitySet<WeatherForecast>("WeatherForecast").EntityType.HasKey(w=>w.TemperatureC);
若是數據模型使用的主鍵,在函數中籤名爲key
,大部分操做都很正常;若是使用id
就會出現各類形形色色的問題,好比不能正確識別函數重載、沒法加載路由等問題。感受和那個Conventional Routing有關係,實在是折騰不動了,老實使用key
算了。
返回數據數量是正確的,可是隻能返回主鍵id,其餘屬性統統沒有。這是由於原來使用ODataModelBuilder
已經不能正確工做了,如今須要更換成ODataConventionModelBuilder
才能夠正常工做映射。
以前版本返回的都是默認的小寫字母開頭的CamelCase,這個版本默認直接返回PascalCase,對前端不是很友好,須要設置一下轉換才能夠。
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EnableLowerCamelCase();
實體類在abstract基類中的屬性,仍是本質上仍是屬於基類,默認狀況不在EDM中註冊也是能夠訪問的,可是若是設置非默認的行爲(好比設置了大小寫),那會出現沒法訪問基類屬性的現象(基類行爲和實體類行爲不一致),這個時候須要在EDM中對基類進行註冊(即便沒有對應的Controller或者其餘引用),參考這個回答。
最後貼一下能夠正常運行的代碼:
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.OData.Formatter; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; using Microsoft.OpenApi.Models; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Text; namespace WebApplication2 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtRegisteredClaimNames.Sub, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = Configuration["Jwt:Issuer"], ValidAudience = Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) }; //options.Authority = "https://222.31.160.20:5001"; }); services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }); }); services.AddOData(opt => opt.AddModel("api", GetEdmModel()).Expand().Filter().Count().OrderBy().Filter()); services.AddControllers( options => { foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0)) { outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata")); } foreach (var inputFormatter in options.InputFormatters.OfType<ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0)) { inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata")); } }); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication2", Version = "v1" }); var filePath = Path.Combine(System.AppContext.BaseDirectory, "WebApplication2.xml"); c.IncludeXmlComments(filePath); c.DocInclusionPredicate((name, api) => api.HttpMethod != null); }); } private IEdmModel GetEdmModel() { ODataModelBuilder builder = new ODataModelBuilder(); var configuration = builder.EntitySet<WeatherForecast>("WeatherForecast").EntityType.HasKey(w=>w.TemperatureC); return builder.GetEdmModel(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (true) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication2 v1")); } app.UseCors(); app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); app.UseStaticFiles(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }