仅公开路由的 .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 路由

如何将完整的数据库架构公开为 OData 服务

不能将“Microsoft.AspNet.OData.Routing.ODataRoute”与端点路由一起使用。 ASP Net Core 2.2 的异常

AWS Api Gateway 是不是支持 OData?

Breeze、OData 和无 EF

从 DMZ 服务器访问 OData