武装你的WEBAPI-OData常见问题
本文属于OData系列
目录
- 武装你的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]
无法正常工作,导致权限认证无法正常进行。
环境
运行环境如下:
- ASP.NET CORE WEBAPI ON .NET 5.0
- Microsoft.AspNetCore.Authentication.JwtBearer 5.0.2
- Swashbuckle.AspNetCore 5.6.3
- Microsoft.AspNetCore.OData 8.0.0-preview3
由于Microsoft.AspNetCore.OData.Versioning.ApiExplorer
这个库不支持新版的OData
,所以版本控制只能使用OData 8.0.0自带的路由方式控制。
常见问题
- 提示Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "GET api/WeatherForecast" for actions - Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
路由的形式有了变化,OData 8.0.0中,在Controller上标记了ODataRoutePrefix之后,不要标记无参数的ODataRoute。现在ODataRoute会从ODataRoutePrefix开始路由,如果标记无参数的ODataRoute,实际上相当于标记了两次,则系统会认为有两个相同的方法,操作重复路由。对于一个有参数,一个无参数的,可以给有参数的方法标记[ODataRoute("id")]。有一个例外,如果参数名称是key
,那么可以不标记。
注意,请不要直接使用[HttpGet("id")]的形式给OData指定路由,这个形式会直接忽略掉OData直接从/
开始路由。
其实我也觉得新的方式才是更合理的。
- 提示 Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Ambiguous HTTP method for action - Microsoft.AspNetCore.OData.Routing.Controllers.MetadataController.GetMetadata (Microsoft.AspNetCore.OData). Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0
我推测应该是bug,在Controller方法只有一个Get并且明确标志了[HttpGet]的形式,依然提示错误。这个问题可以参考这里。
services.AddSwaggerGen(options =>
{
// ........................
options.DocInclusionPredicate((name, api) => api.HttpMethod != null);
});
- 提示System.InvalidOperationException: No media types found in 'Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatter.SupportedMediaTypes'. Add at least one media type to the list of supported media types.
这个我估摸也是bug,请注意,必须将services.AddOData放在services.AddControllers之前,否则在Controller中无法识别ODataOutputFormatter,然后参考这里解决问题。
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"));
}
});
- 提示ODM的问题The entity set 'WeatherForecast' is based on type 'WebApplication2.WeatherForecast' that has no keys defined.
现在EdmBuilder不能识[Key]来进行主键的标记了,需要显式添加HasKey
:
var configuration = builder.EntitySet<WeatherForecast>("WeatherForecast").EntityType.HasKey(w=>w.TemperatureC);
诡异的key与id问题
如果数据模型使用的主键,在函数中签名为key
,大部分操作都很正常;如果使用id
就会出现各种形形色色的问题,比如不能正确识别函数重载、无法加载路由等问题。感觉和那个Conventional Routing有关系,实在是折腾不动了,老实使用key
算了。诡异的返回所有数据只有主键id的问题
返回数据数量是正确的,但是只能返回主键id,其他属性通通没有。这是因为原来使用ODataModelBuilder
已经不能正确工作了,现在需要更换成ODataConventionModelBuilder
才可以正常工作映射。返回首字符大小写的问题
之前版本返回的都是默认的小写字母开头的CamelCase,这个版本默认直接返回PascalCase,对前端不是很友好,需要设置一下转换才可以。
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
- 实体类继承abstract导致的找不到基类的属性问题
实体类在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();
});
}
}
}
武装你的WEBAPI-OData常见问题的更多相关文章
- OData – the best way to REST–实例讲解ASP.NET WebAPI OData (V4) Service & Client
一.概念介绍 1.1,什么是OData? 还是看OData官网的简单说明: An open protocol to allow the creation and consumption of quer ...
- AspNet WebApi OData 学习
OData介绍:是一个查询和更新数据的Web协议.OData应用了web技术如HTTP.Atom发布协议(AtomPub)和JSON等来提供对不同应用程序,服务 和存储的信息访问.除了提供一些基本的操 ...
- AspNet.WebAPI.OData.ODataPQ
AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务 AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔) AspNet. ...
- AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)
AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI OData 协议下,查询分页.或者是说 本人在使用Asp.Net webAPI 做服务接口时写的一个分页 ...
- 主攻ASP.NET MVC4.0之重生:Asp.Net MVC WebApi OData
1.新建MVC项目,安装OData Install-Package Microsoft.AspNet.WebApi.OData -Version 4.0.0 2.新建WebAPI Controller ...
- AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)(转)
出处:http://www.bubuko.com/infodetail-827612.html AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI ODat ...
- [转]Composite Keys With WebApi OData
本文转自:http://chris.eldredge.io/blog/2014/04/24/Composite-Keys/ In our basic configuration we told the ...
- [转]OData – the best way to REST–实例讲解ASP.NET WebAPI OData (V4) Service & Client
本文转自:http://www.cnblogs.com/bluedoctor/p/4384659.html 一.概念介绍 1.1,什么是OData? 还是看OData官网的简单说明: An open ...
- webAPi OData的使用
一.OData介绍 开放数据协议(Open Data Protocol,缩写OData)是一种描述如何创建和访问Restful服务的OASIS标准. 二.OData 在asp.net mvc中的用法 ...
- 基于Angular+WebAPI+OData的增删改查
对于在ASP.NET WebAPI中怎么使用OData,已经在我前面的日志中的说明, 在ASP.NET Web API中使用OData 在这个示例中.我新建了一个Order的实体,在前端使用Angul ...
随机推荐
- 容器编排系统K8s之NetworkPolicy资源
前文我们了解了k8s的网络插件flannel的基础工作逻辑,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14225657.html:今天我们来聊一下k8s上 ...
- HDFS 修改默认副本数
描述:将HDFS副本数修改为2第一步:将HDFS上已有文件副本数修改为2 hdfs dfs -setrep 2 -R -w / 第二步:修改dfs.replication值为2(页面上操作),然后重启 ...
- python函数----名称空间和作用域
一 名称空间 名称空间即存放名字与对象映射/绑定关系的地方. 对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系. 在程序执行 ...
- 【小菜学网络】MAC地址详解
上一小节介绍了以太网帧的结构,以及帧中各个字段的作用.参与以太网通讯的实体,由以太网地址唯一标识.以太网地址也叫做 MAC 地址,我们对它仍知之甚少. 以太网地址在不同场景,称谓也不一样,常用叫法包括 ...
- 使用uiautomatorviewer报错Error obtaining UI hierarchy
现象:使用uiautomatorviewer报错Error obtaining UI hierarchy 解决方法:经验证关闭appium,再重新获取,就不会报错 (python运行了app代 ...
- Go 的定时任务模块 Cron 使用
前言 新项目是Golang作为开发语言, 遇到了些新的坑, 也学到了新的知识, 收获颇丰 本章介绍在Go中使用Cron定时任务模块来实现逻辑 正文 在项目中, 我们往往需要定时执行一些逻辑, 举个例子 ...
- 请求接口获取的json 字符串 前后不能有 双引号
请求接口获取的json 字符串 前后不能有 双引号 否则JSON.parse 转换会报错
- linux下的文件类型
在Linux中一切设备皆文件,首先来看一下Linux下的文件都有哪些分类,也就是文件类型 文件类型:普通文件(包括shell脚本,文档,音频,视频).目录文件.设备文件(又细分为字符设备文件和块设备文 ...
- 【Linux】rsync 守护进程的配置
环境 centos7.2 1.首先查看是否安装rsync的相关包 rpm -qa | grep rsync rsync-3.1.2-4.el7.x86_64 如果没安装就yum install rsy ...
- CodeMonke少儿编程第1章 step与turn
第1章 step与turn 目标 了解游戏舞台的各组成部分 掌握step和turn指令的用法 说起计算机,对于不了解它的人来说,也许会感到有些神秘,其实不然,它不过是能够接收指令并且按照指令执行的一种 ...