本文属于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(); }); } } }