仅公开路由的 .NET OData API 子集(排除 API 返回 404)
Posted
技术标签:
【中文标题】仅公开路由的 .NET OData API 子集(排除 API 返回 404)【英文标题】:Expose only a subset of .NET OData APIs for a route (return 404 for excluded APIs) 【发布时间】:2018-12-21 04:16:58 【问题描述】:背景/上下文:
我们有两条路线,路线前缀不同:
-
路由 1 前缀:
/api
Route 2 前缀:/api/partial
目前,我们对两个路由前缀使用相同的 EdmModel。 (请参阅第一个代码片段,名为“我们当前所做的事情”)。
我们想要什么:
我们只需要允许 Route 2 的 API 功能子集:/api/partial
。当有人试图访问对“部分”EdmModel 不可用的 API 时,我们希望返回 404
例子:
-
我们希望为
/api/parial/products
返回404
,其中products
未在此“部分”API 路由中定义。
我们仍希望将/api/products
路由到控制器方法
我们的尝试:
使用第二个 EdmModel,它仅包含完整 EdmModel 中可用实体的子集。 (参见第二个代码片段,名为“我们想要做什么:”。)
问题:
服务启动时出现错误:
The path template 'products' on the action 'Export' in controller 'Products' is not a valid OData path template. Resource not found for the segment 'products'.)
我对正在发生的事情的最佳猜测是 .NET OData 库会扫描所有 OData 控制器、函数、操作,并期望它们中的每一个都在 EdmModel 中为每个路由显式定义。如果这是真的,那么这个解决方案(初始化一个新的 EdmModel)很可能不起作用......
不支持吗?如果没有,还有哪些其他选择可以实现这一目标?我们必须在控制器 API 函数中显式返回 404 吗?这需要在 API 函数中分析“api/subset”的路径,这在我看来就像是 hack。
我们目前的工作:
private static IEdmModel GetFullEdmModel()
var builder = new ODataConventionModelBuilder();
var orders = builder.EntitySet<Order>("orders");
orders.EntityType.HasKey(o => o.Id);
orders.EntityType.Property(o => o.Id).Name = "id";
var products = builder.EntitySet<Product>("products");
products.EntityType.HasKey(p => p.Id);
products.EntityType.Property(p => p.Id).Name = "id";
products.EntityType.Action("Export").Returns<ExportResponse>();
return builder.GetEdmModel();
protected override void Register(HttpConfiguration config)
base.Register(config);
var model = GetFullEdmModel();
var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(config, model);
// Map route 1 to model
config.MapODataServiceRoute(
routeName: "route1",
routePrefix: "/api",
model: model,
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
// Map route 2 to model
config.MapODataServiceRoute(
routeName: "route2",
routePrefix: "/api/partial", // different route prefix
model: model, // but it uses the same model
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
我们想做的事:
private static IEdmModel GetPartialEdmModel()
var builder = new ODataConventionModelBuilder();
// Include only one entity
var orders = builder.EntitySet<Order>("orders");
orders.EntityType.HasKey(o => o.Id);
orders.EntityType.Property(o => o.Id).Name = "id";
return builder.GetEdmModel();
protected override void Register(HttpConfiguration config)
base.Register(config);
// Map route 1 to model
var model = GetFullEdmModel();
var modelConventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(config, model);
config.MapODataServiceRoute(
routeName: "route1",
routePrefix: "/api",
model: model, // use standard full model
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
// Map route 2 to a new partial model: partialModel
var partialModel = GetPartialEdmModel();
var partialModelConventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(config, model);
config.MapODataServiceRoute(
routeName: "route2",
routePrefix: "/api/partial", // different route prefix
model: partialModel, // use a sparate, partial edm model ( a subset of the full edm model )
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
【问题讨论】:
Yikes tightly coupling 您对 DataAccess 的数据访问对您的安全性而言听起来像是一场噩梦。你为什么不写一个用于数据访问的类,然后通过两个接口公开它,一个有 super 方法,一个没有? @ErikPhilips 这是一个缩小的示例,实际的服务架构非常庞大,多个代理服务都共享相同的糟糕路由约定。这就是我正在处理的问题,不幸的是我没有能力重新设计整个服务。 只需使用Adapter Pattern。 @ErikPhilips 感谢您的提示,但您能否更清楚地说明您的建议?您是否建议以某种方式将适配器模式应用于 EdmModel? 为什么不直接从部分控制器添加[ODataRoute(products)] Get(FromODataUri] String products) return NotFound();
?
【参考方案1】:
您需要两个不同的模型或更多 intelligent
模型,其条件为实体 products
并非在所有路径中都可用,而仅在 /api/products
中可用。
一般来说,错误消息已经很好地解释了它,但换句话说,也许你只是需要它。
我认为更简洁的方法是为每条路线提供一个自己的模型,然后在里面添加或删除你需要的任何东西都很容易。 如果您将所有内容混合在一个模型中,那么在添加或更改新路线时,它将始终处于建设状态。
// Map route 1 to model
config.MapODataServiceRoute(
routeName: "route1",
routePrefix: "/api",
model: GetApiModel(),
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
// Map route 2 to model
config.MapODataServiceRoute(
routeName: "route2",
routePrefix: "/api/partial", // different route prefix
model: GetApiPartialModel(),
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
这是一个概念,我对该代码中的表示法不太确定,因此您可能需要对其进行一些调整。
或者,如果您真的只想使用一种模型,请尝试如下:
// Map route 1 to model
config.MapODataServiceRoute(
routeName: "route1",
routePrefix: "/api",
model: GetFullEdmModel("/api"),
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
// Map route 2 to model
config.MapODataServiceRoute(
routeName: "route2",
routePrefix: "/api/partial", // different route prefix
model: GetFullEdmModel("/api/partial"), // but it uses the same model
pathHandler: new CustomBIODataPathHandler(),
routingConventions: conventions);
然后你可以使用参数来实现任何条件或开关。
除此之外,您可能在底部所需的代码中存在错误:
// Map route 1 to model
var model = GetFullEdmModel();
var modelConventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(config, model);
config.MapODataServiceRoute(
routeName: "route1",
routePrefix: "/api",
model: model, // use standard full model
pathHandler: new CustomBIODataPathHandler(),
routingConventions: modelConventions);
查看最后一行,然后必须针对两个块进行调整。 我以前从未关心过每个配置块上方的两行,所以主要问题可能是并非所有变量都适合在一起,您必须检查所有细节。
【讨论】:
大卫,这正是我想要做的:定义这个intelligent model
,用condition
可以指定一个API 不可用(但是,我非常希望将API 列入白名单函数,而不是将它们列入黑名单。如果你能提供一个概念证明,我会接受这个答案
@JamesWierzba 已更新,希望它能以某种方式工作。
@JamesWierzba 再次
大卫,这与我尝试过的解决方案有何不同?我试过这个(第一个代码示例),Web API OData 库不支持这个。它在启动时崩溃并出现错误,因为它似乎期望服务中的每个 API 方法(即具有[OData ...]
属性的所有内容)都映射到每个模型。
@JamesWierzba 抱歉,也许我最后的补充是有帮助的。我没有这样的环境,所以我无法测试任何东西。以上是关于仅公开路由的 .NET OData API 子集(排除 API 返回 404)的主要内容,如果未能解决你的问题,请参考以下文章
使用 ASP.NET Core OData 8.0 映射动态 OData 路由
不能将“Microsoft.AspNet.OData.Routing.ODataRoute”与端点路由一起使用。 ASP Net Core 2.2 的异常