Background/Context:
We have two routes, with different route prefixes:
/api
/api/partial
Currently, we use the same EdmModel for both route prefixes. (See the first code snippit, named "What we currently do").
What we want:
We need to only allow a subset of API functionality for Route 2: /api/partial
. We want to return 404
when someone tries to access an API that is not available to the "partial" EdmModel
Example:
404
for /api/parial/products
, where products
is not defined in this "partial" API route./api/products
to the controller methodWhat we've tried:
Using a second EdmModel, that contains only a subset of the entities available in the full EdmModel. (See the second code snippit, named "What we want to do:".)
Problem:
We get an error on service startup:
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'.)
My best guess at what is happening is that the .NET OData library scans all of the OData controllers, functions, actions and expect each of them to be explicitly defined in the EdmModel for each route. If this is true, then this solution (initializing a new EdmModel) will likely not work...
Is this not supported? If not, what other options are there to accomplish this? Must we explicitly return 404 in the controller API function? This would require analyzing the path for "api/subset" in the API function, which seems to me like a hack.
What we currently do:
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);
}
What we want to do:
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);
}
You need two different models or a more intelligent
model with conditions as entitiy products
is not available in all paths but only in /api/products
.
In general the Error-message is explaining it already quite well but perhaps you just need it in other words.
I think the cleaner way is to provide an own model for each route, then it's quite easy to add or remove whatever you need inside.
If you mix everything in one model it will always be under construction if a new route is added or changed.
// 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);
It's a concept and I'm not so firm with the notation in that code so you might have to adjust it a bit.
Alternatively, if you really want to use only one model just try it like this:
// 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);
Then you can use the parameter to implement any conditions or a switch.
Beside that you've probably faults in your desired code in the bottom:
// 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);
See the last line, that has to be adjusted for both blocks then. I never took care before about the two lines above each config-block, so primary the problem is probably that not all variables are fitting together and you've to check it in all details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With