$Metadata with WebAPi OData Attribute Routing Not Working - c#

I'm using OData Attribute Routing for an OData endpoint. Here is an example of what I have:
[ODataRoutePrefix("Profile")]
public class ProfileODataController : ODataController
{
[ODataRoute]
[EnableQuery]
public IHttpActionResult Get()
{
var repo = new Repositories.ProfileRepository();
return Ok(repo.GetProfiles());
}
[ODataRoute("({key})")]
[EnableQuery]
public IHttpActionResult Get([FromODataUri] string key)
{
var repo = new Repositories.ProfileRepository();
var result = repo.GetProfiles().SingleOrDefault(x => x.Id== key);
if (result == null) return NotFound();
return Ok(result);
}
}
Here is my set up:
config.MapODataServiceRoute("odata", "odata", ModelGenerator.GetEdmModel());
Here is my EdmModel Generation:
public static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Profile>("Profile").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
The urls /odata/Profile and /odata/Profile('someid') both work, but when I try to access the $metadata endpoint (/odata/$metadata#Profile), I get the following error:
{"Message":"No HTTP resource was found that matches the request URI 'http://****/odata/$metadata'.","MessageDetail":"No type was found that matches the controller named 'Metadata'."}
Do I need to create a controller/action for serving the metadata? If so, how is that action implemented?

Turns out it had to do with my replacement of the IAssembliesResolver.
I had implemented a custom version to provide only the component assemblies that I had implemented controllers in. However, as the error said, it couldn't find a controller named MetadataController. Turns out, OData implements one: System.Web.OData.MetadataController, which provides for the $metadata keyword.
Since I had implemented my own IAssembliesResolver, the System.Web.OData assembly wasn't being included, and $metadata failed. Once I discovered this, and updated my assembly resolver to explicitly include the OData assembly, it now works as it should.

Related

EnableQuery doesn't work for same functions but different endpoint

Some of my endpoints are wrapped with [EnableQuery] decorators. The first one I have is
[HttpGet]
[EnableQuery(PageSize = ITEMS_PER_PAGE)]
public async Task<ActionResult> GetAsync()
{
var projects = await _projectRepository.GetAllAsync();
return Ok(projects);
}
while the second one is
[HttpGet("TestName", Name = "TestName")]
[EnableQuery(PageSize = ITEMS_PER_PAGE)]
public async Task<ActionResult> GetTest()
{
var projects = await _projectRepository.GetAllAsync();
return Ok(projects);
}
As you can notice, their bodies are the SAME, same repository and same function. However, the first endpoint returns me a json that looks like
{
#odata.context: "https://localhost:44327/api/$metadata#Project",
#odata.count: 64,
value: [20],
#odata.nextLink: "https://localhost:44327/api/project?$count=true&$orderby=CreateDate%20desc&$skip=40",
}
while the second one returns
[
{},
.
.
.
{},
]
or those #odata.x things aren't present. I'm not that familiar with [EnableQuery] and OData but somewhere in the code I found this
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Project>("Project");
builder.EntitySet<Project>("TestName");
return builder.GetEdmModel();
}
app.UseEndpoints(endpoints =>
{
endpoints.Select().Filter().OrderBy().Count().MaxTop(50);
endpoints.MapODataRoute("odata", "api", GetEdmModel());
});
Project is a class I have that represents a database model and the controller name is ProjectController. I've seen multiple documentations online that inherit from ODataController. However I don't do that in my case. How can I make the second endpoint work like the first one? I also couldn't find that much info about builder.EntitySet<Project>("TestName");, can someone explain to me what this is? Should "TestName" be the name of the endpoint?

ASP.NET Web API versioning with URL Query gives "duplicate route" error

First, a little disclaimer: I have already created a GitHub issue for this at the aspnet-api-versioning repo. The content of this question is basically the same as the content in that github issue.
I am using ASP.NET Web API on .NET 4.5.2.
My example API looks like this:
namespace App.Backend.Controllers.Version1
{
[ApiVersion("1.0")]
public class SomeController: ApiController
{
[HttpGet]
[Route("api/entity/{id:int:min(1)}")]
public async Task<IHttpActionResult> ApiAction(int id) //Already running in production
{
//Accessible by using ?api-version=1.0 OR by omitting that since this is the default version
return Ok();
}
}
}
namespace App.Backend.Controllers.Version2
{
[ApiVersion("2.0")]
public class SomeController : ApiController
{
[HttpGet]
[Route("api/entity/{id:int:min(1)}")]
public async Task<IHttpActionResult> ApiAction(int id)
{
//Accessible by using ?api-version=2.0 OR by omitting that since this is the default version
return Ok();
}
}
}
The config is as follows:
// Add versioning
config.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
});
When I send a request, though, the following happens:
System.InvalidOperationException: A route named 'RegisterHours' is already in the route collection. Route names must be unique.
Duplicates:
api/some/ApiAction
api/some/ApiAction
This is weird to me because in the wiki there is an example exactly like my situation
I'd like to use the ?api-version={version} option but it looks I have no choice to use the URL path version now (api/v1.0/some/apiAction and api/v2.0/some/apiAction. If that's true, I guess I have to add another Route to every existing action which will be like api/v{version:apiVersion}/controller/action to allow them to use v1.0 so it will be uniform in the entire application?
What do you guys advise? I could just use /v2.0/ in the URL of version 2 of the API I guess, but I'd prefer the query string version.
It doesn't show it in the example, but the error message:
System.InvalidOperationException: A route named 'RegisterHours' is already in the route collection. Route names must be unique.
means that there are multiple entries with the same name in the route table. If I had to guess, the attribute routes are actually being defined as:
[Route("api/entity/{id:int:min(1)}", Name = "RegisterHours")]
...or something like that.
Unfortunately, the route table is not API version-aware. The names in the route table must be unique. When you specify the name in the RouteAttribute, it causes this issue. The only real way around it is to use unique route names; for example, Name = "RegisterHoursV1" and Name = "RegisterHoursV2".
Aside: you don't need:
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) }
};
configuration.MapHttpAttributeRoutes( constraintResolver );
unless you are versioning by URL segment.
After you have fixed the duplicate "RegisterHours" (as per the git hub issues page responses) you should also ensure you have the constraintResolver setup in your startup.cs
public void Configuration( IAppBuilder builder )
{
// we only need to change the default constraint resolver for services that want urls with versioning like: ~/v{version}/{controller}
var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) } };
var configuration = new HttpConfiguration();
var httpServer = new HttpServer( configuration );
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
configuration.AddApiVersioning( o => o.ReportApiVersions = true );
configuration.MapHttpAttributeRoutes( constraintResolver );
builder.UseWebApi( httpServer );
}
Otherwise your attributes to change the route (api/entity) won't work because the route doesn't match the controller name "Some" and so won't match the default routing ie. ~\api\controllername\
public class ***Some***Controller : ApiController
{
[HttpGet]
[Route( "api/***entity***/{id:int:min(1)}" )] <--- won't work without the constraint resolver
public async Task<IHttpActionResult> ApiAction( int id )
{
//Accessible by using ?api-version=2.0 OR by omitting that since this is the default version
return Ok();
}

Can't invoke single query GET in WebApi OData

I have web api with OData controller. Models is
public class UserDto
{
public int UserDtoId {get;set;}
public string Name {get;set;}
}
in controller I have two method
[EnableQuery]
public IQueryable<UserDto> Get();
[EnableQuery]
public SingleResult<UserDto> GetUser([FromODataUri] int key);
OData config is:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<UserDto>("Users").EntityType.HasKey(e=>e.UserDtoId).Name = "User";
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
when I try invoke odata/Users(123), the odata try to invoke first get not a get with a key and return me all record from table. When I comment out first get method there is none GET method for this URI access at all. Where I make a mistake?
I try to make [ODataRoute] its doesnt change nothing.
In your code, your two functions are Get() and GetUser(int key)
They should both be 'Get'
Also not sure if it matters, but for the Get(int) one, if it still doesnt work, try changing SingleResult<UserDto> to just UserDto

Where does WebAPI 2.2 OData v4 [EnableQuery] apply?

Where is it correct/incorrect to apply the EnableQueryAttribute as of Jan 2015?
The document linked below:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/create-an-odata-v4-endpoint
Says:
The [EnableQuery] attribute enables clients to modify the query, by using query options such as $filter, $sort, and $page. For more information, see Supporting OData Query Options.
The following linked document:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options
Says:
The EnableQuerySupport method enables query options globally for any controller action that returns an IQueryable type.
But this document for OData 4 on WebApi 2.2 has put it on actions returning IHttpActionResult:
http://blogs.msdn.com/b/webdev/archive/2014/03/13/getting-started-with-asp-net-web-api-2-2-for-odata-v4-0.aspx
[ODataRoutePrefix("Teams")]
public class TeamsEntitySetController : ODataController
{
private readonly LeageContext _leage = new LeageContext();
[EnableQuery]
[ODataRoute]
public IHttpActionResult GetFeed()
{
return Ok(_leage.Teams);
}
[ODataRoute("({id})")]
[EnableQuery]
public IHttpActionResult GetEntity(int id)
{
return Ok(SingleResult.Create<Team>(_leage.Teams.Where(t => t.Id == id)));
}
}
I'm going crazy trying to find up-to-date, accurate and consistent documentation on OData v4 / WebApi 2.2.
Which is correct today?
Use global configuration (instance of an HttpConfiguration object) and call
config.Filters.Add(new EnableQueryAttribute()
{
PageSize = 2
// .. other settings
});
this works

ASP.NET Odata Web API CRUD Operations Not Working

I have spent a about 2 days setting up my Odata Web API project that was before a simple Asp.net mvc4 project.
But still I am not successful in operating even CRUD Operations.
It says this:
<m:message xml:lang="en-US">
No HTTP resource was found that matches the request URI 'http://localhost:53208/odata/Product'.
</m:message>
<m:innererror>
<m:message>
No action was found on the controller 'Product' that matches the request.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
It means its reaching the Controller but not finding actions or there is some problem in my routing settings.
I have following in my WebApiConfig:
config.Routes.MapODataRoute("OData","odata",GetEdmModel());
My getEdmModel method:
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Product");
builder.EntitySet<OfferedProduct>("OfferedProduct");
IEdmModel model = builder.GetEdmModel();
return model;
}
My controller is like this:
public class ProductController : EntitySetController<Product,int>
{
private OfferAssistantDbContext db = new OfferAssistantDbContext();
List<Product> Products = new OfferAssistantDbContext().Products.ToList();
// GET api/Product
[Queryable(PageSize = 10)]
public override IQueryable<Product> Get()
{
return Products.AsQueryable();
}
// GET api/Product/5
public Product GetProduct([FromODataUri] int id)
{
return Products[id];
}
/// and so on... but for this time lets work only on GET operation
Now when I write this in my browser:
http://localhost:53208/odata/Product
it says the error I showed above..
Please guide me where is the problem?
I believe that your controller needs to inherit from ODataController:
public class ProductController : ODataController

Categories