OData API Composite key and Swagger Settings - asp.net-core-mvc

I Encounter an issue with Swagger.
I have a .Net Core Wep API with one entity using a composite key.
The key is declared with the following syntax:
public class EntityConfig : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
EntityTypeConfiguration<Entity> entity = builder.EntitySet<Entity>("Entity").EntityType;
entity.HasKey(x => new { x.FirstId, x.SecondId});
}
}
My controller declare my delete API point like this :
[ODataRoutePrefix("Entity")]
public class EntityController : ODataController
{
///ctor with context injection
[HttpDelete]
public async Task<IActionResult> Delete([FromODataUri] int keyFirstId, [FromODataUri] int keySecondId)
{
///Delete Behavior
}
}
I can call my Delete method with Postman using this request :
http://localhost:8090/api/Entity(FirstId=1,SecondId=1)
Eveything works with Postman and my website, but when i try to launch Swagger i get this error :
Microsoft.OData.ODataException: The number of keys specified in the URI does not match number of key properties for the resource.
How can i set Swagger to accept my parameters as composite key while keep working with OData ?
Thanks

Ok i finally find a solution.
I have change my parameter into my controller and delete the 'key' prefix on my parameter.
Then i have change my request by this version:
http://localhost:8090/api/Entity(FirstId=0,SecondId=0)?FirstId=1&SecondId=1
The request doesn't work without the first parameter declaration (FirstId=0,SecondId=0), i supposed that syntax force the parameter to by see as valid by my controller.
Anyway, everything works and swagger doesn't complain anymore so i guess it's a good start.
Thanks

Related

How can I resolve the error I am getting in web api?

**web-api**
As u can see I have to develop different api . For this I have created a MasterController. But I am getting an problem. I am using Postman for testing and I am getting the following problem. Please help me to resolve this issue. I am getting multiple match points. Please help me resolve the issue.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using VENUS.HRMS.API.Filters;
using VENUS.HRMS.DATA.Data;
using VENUS.HRMS.DATA.Models;
namespace VENUS.HRMS.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class MasterController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
[AuthorizationFilter]
public IEnumerable<TblMstEmpRole> Get()
{
var emprole = new EmpRoleData().GetMstEmpRole();
return emprole;
}
[HttpGet]
[AuthorizationFilter]
public IEnumerable<TblMstState> GetState()
{
var state = new StateData().GetMstState();
return state;
}
[HttpGet]
[AuthorizationFilter]
public IEnumerable<TblMstCity> GetCity()
{
var city = new CityData().GetMstCity();
return city;
}
}
}
The issue is exactly what error says: The request matched multiple endpoints
Remember when calling an API, the function name does not matter. The function name is only for "Internal" use within your C# applicaiton. The name of the API endpoint correlates to it's route. This route is set with either the [Route()] attribute or within the [HttpGet()] attribute.
For example you have 3 functions:
[HttpGet]
public IEnumerable<TblMstEmpRole> Get()
[HttpGet]
public IEnumerable<TblMstState> GetState()
[HttpGet]
public IEnumerable<TblMstCity> GetCity()
The full route for these three functions respectively are:
Get --> /Master/
Get --> /Master/
Get --> /Master/
Yes all thee are the same. And that is the exact issue you are having. To fix this change the Route for your endpoints like this:
[HttpGet]
public IEnumerable<TblMstEmpRole> Get()
[HttpGet("State")]
public IEnumerable<TblMstState> GetState()
[HttpGet("City")]
public IEnumerable<TblMstCity> GetCity()
Now the routes will look like this:
Get --> /Master/
Get --> /Master/State
Get --> /Master/City
The rule is:
You can only have one route name per Http operation.
Different HTTP Methods
[HttpGet] is one of the attributes which has a GET method, however, there are many other methods such as POST, HEAD, PUT, OPTIONS, DELETE, CONNECT, TRACE and PATCH.
To utilize them through an API endpoint all you need to do is add it to the function. All the attributes have the [Http***] format. Example Post and Put will be:
[HttpPost]
[HttoPut]
And has mentioned a little above, you can only have one Route for each method. See examples below:
// PATH: GET --> /Master/
[HttpGet]
public IEnumerable<TblMstEmpRole> GETGet()
// PATH: POST --> /Master/
[HttpPost]
public IEnumerable<TblMstEmpRole> POSTGet()
The above example is VALID. Even though we have two functions with the same route, they do have different Http Methods and as such are uniquely identifiable.
Why Different Methods
Each method has it's own quirks and uses. Depending on the method, some things are allowed and some are not. When a client makes a query to a server and sends something, that is called a Http Request. The method for either getting or setting it is the Http Method, example GET, POST, PUT...
A Http Request has the following structure:
Headers
Parameters
Body
Footer
If we were to use the GET method, then you are telling the endpoint that it should ignore the Body of the request.
If we were to use the HEAD method then we are telling the endpoint that we are only interested in the Headers section of the request.
Now to figure out which to use and when can be tricky, but as a basic rule of thumb you can use this:
GET - You are only fetching data and not sending anything in the body
POST - When you are creating something in the database or sending data in the body
PUT - Similar to Post, but only when you are updating something
DELETE - When you are removing something.
These are the basic 4 any beginner to intermediate should be aware of. For more information on what method to use see: https://www.w3schools.com/tags/ref_httpmethods.asp

How to just get the data using CRUD POST method?

I have developed Small Spring boot Rest api app. I can able to get the data or create new record and search with paging and sorting.
Now i'm looking for provide input data in body to get the data instead of providing in URL with GET method. Is this method also default function ? Please advise.
public interface CodeTextRepository extends PagingAndSortingRepository<CodeText, Long> {
}
How to write POST method to just get the data ?
http://localhost:8080/api/code
method : POST
{
"code":1
}
If I understand you correctly, you want to create a controller that will get the a model as body parameter ({ "code": 1 }) in a POST method and then do something with it.
To do that, you can create a controller that looks like the following (I inserted pseudo-code as an example):
#RestController
#RequestMapping(value = "/api/code")
public class CodeTextController {
private CodeTextRepository codeTextRepository;
// constructor injection
public CodeTextController(CodeTextRepository codeTextRepository) {
this.codeTextRepository = codeTextRepository;
}
#PostMapping
public CodeText postCodeText(#RequestBody CodeTextRequest codeTextRequest) {
// some code to get from the DB
return codeText;
}
}
public class CodeTextRequest {
private int code;
// getters and setters
}
Simply add Accept header to the request, like
accept: application/json
Spring Data-Rest will return the body after a POST request if either the returnBodyOnCreate flag was explicitly set to true in the RepositoryRestConfiguration OR if the flag was NOT set AND the request has an Accept header.
You can set the flag directly during configuration, or you can set it via the application.properties:
spring.data.rest.returnBodyOnCreate = true
you can also set it separately for update:
spring.data.rest.returnBodyOnUpdate = true
---- edit
Maybe I misunderstood your question. If you simply want to GET an existing data using POST method, then DO NOT DO IT AT ALL! That's not a REST API any more. There must be some reason you want to do it, but you should try do resolve that original problem instead in another way!

How to get Swagger UI to display similar Spring Boot REST endpoints?

I have a controller class with two endpoints
#RestController
#RequestMapping
public class TestController {
#RequestMapping(
value= "/test",
method = RequestMethod.GET)
#ResponseBody
public String getTest() {
return "test without params";
}
#RequestMapping(
value= "/test",
params = {"param"},
method = RequestMethod.GET)
#ResponseBody
public String getTest(#PathParam("param") int param) {
return "test with param";
}
}
One has a parameter, one doesn't, and the both work.
If I use curl or a web browser to hit the endpoints
http://localhost:8081/test
returns
test without params
and
http://localhost:8081/test?param=1
returns
test with param
but the swagger ui only shows the one without a parameter.
If I change the value in the request mapping for the request with a parameter to
#RequestMapping(
value= "/testbyparam",
params = {"param"},
method = RequestMethod.GET)
Swagger UI displays both endpoints correctly, but I'd rather not define my endpoints based on what swagger will or won't display.
Is there any way for me to get swagger ui to properly display endpoints with matching values, but different parameters?
Edit for Clarification:
The endpoints work perfectly fine; /test and /test?param=1 both work perfectly, the issue is that swagger-ui won't display them.
I would like for swagger ui to display the endpoints I have defined, but if it can't, then I'll just have to live with swagger-ui missing some of my endpoints.
Edit with reference:
The people answering here: Proper REST formatted URL with date ranges
explicitly say not to seperate the query string with a slash
They also said "There shouldn't be a slash before the query string."
The issue is in your Request Mapping, The second method declaration is overriding the first method. As Resource Mapping value is same.
Try changing the second method to below. As you want to give input in QueryParam rather than path variable, you should use #RequestParam not #PathParam.
Note that you have to give /test/, In order to tell Spring that your mapping is not ambiguous. Hope it helps.
#RequestMapping(
value= "/test/",
method = RequestMethod.GET)
#ResponseBody
public String getTest (#RequestParam("param") int param) {
return "test with param"+param;
}
Upon reading clarifications, the issue here is that swagger-ui is doing the correct thing.
You have two controller endpoints, but they are for the same RESOURCE /test that takes a set of optional query parameters.
Effectively, all mapped controller endpoints that have the same method (GET) and request mapping (/test) represent a single logical resource. GET operation on the test resource, and a set of optional parameters which may affect the results of invoking that operation.
The fact that you've implemented this as two separate controller endpoints is an implementation detail and does not change the fact that there is a single /test resource that can be operated upon.
What would be the benefit to consumers of your API by listing this as two separate endpoints in swagger UI vs a single endpoint with optional parameters? Perhaps it could constrain the set of allowed valid query parameters (if you set ?foo you MUST set &bar) but this can also be done in descriptive text, and is a much more standard approach. Personally, I am unfamiliar with any publicly documented api that distinguishes multiple operations for the same resource differentiated by query params.
As per Open API Specification 3
OpenAPI defines a unique operation as a combination of a path and an
HTTP method. This means that two GET or two POST methods for the same
path are not allowed – even if they have different parameters
(parameters have no effect on uniqueness).
Reference - https://swagger.io/docs/specification/paths-and-operations/
This was also raised as an issue but it was closed because OAS3 doesn't allow that -
https://github.com/springdoc/springdoc-openapi/issues/859
Try including the param in the path as below.
#GetMapping("/test/{param}")
public String getTest(#PathVariable final int param) {
return "test with param";
}
I'm unclear exactly what you're attempting to do, but I'll give two solutions:
If you want to have PATH parameters e.g. GET /test & GET /test/123 you can do:
#GetMapping("/test")
public String getTest() {
return "test without params";
}
#GetMapping("test/{param}")
public String getTest(#PathVariable("param") int param) {
return "test with param";
}
If you want query parameters (GET /test and GET /test?param=123) then you need a single endpoint that takes an optional parameter:
#GetMapping("test")
public String getTest(#RequestParam("param") Integer param) {
if(param == null) {
return "test without params";
} else {
return "test with param";
}
}

How do I expose a navigation property over OData 4 and WebApi 2.2?

I have a navigation property on a model, Site.Locality and although its foreign key is serialized and available to consumers (Site.LocalityName) I'd like the locality itself to be available from:
~/Site('A')/Locality
How is this done in OData v4 over WebApi 2.2?
On your controller for the Site entity, add the following action:
// Implies that the controller has [ODataRoutePrefix("Sites")]
[ODataRoute("({name})/Locality")]
public async Task<Locality> GetLocality([FromODataUri] string name)
{
// Add try-catch or null 404 handling.
var site = await this.Repository.GetAsync(new[] { name });
return site.Locality;
}
Obviously, place your own DAL code in there, this is just an example.
It's very clear to see that this is achieved through nothing more complex than a simple route and action on your controller.
That said, there is some mapping happening under the hood. For example, you couldn't just expose any arbitrary navigation property:
[ODataRoute("({name})/Wangachop")]
public string GetWangachop([FromODataUri] string name)
{
return "Wangaaa!";
}
Would yield:
The path template 'Sites({name})/Wangachop' on the action 'GetWangachop' in controller 'Sites' is not a valid OData path template. Found an unresolved path segment 'Wangachop' in the OData path template 'Sites({name})/Wangachop'.

Controller not filtering data in Breeze query in DotNetNuke Module

I am trying to include the basic Breeze sample in a DotNetNuke module (it works fine in a standalone WebAPI project). To simplify things I remove the client and will just refer to the URL JSON calls I make in the Chrome browser.
I can see my metadata and a full list of items, eg:
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/metadata
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos
however, when I try to filter the list from the URL, it always returns the full list, e.g.
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos?=DareId%20eq%204
I think it is something to do with the way I have declared the MapHTTRoute. The problem is that DotNetNuke modules do not have a Global.ascx. I have copied the BreezeWebApiconfig.cs file into my App_Start folder and this does fire when I debug, however DotNetNuke uses mechanism for registering routes:
using DotNetNuke.Web.Api;
namespace SmartThinker.Modules.Framework
{
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("framework", "BreezeApi", "breeze/{controller}/{action}", new[] { "SmartThinker.Modules.Framework.Controllers" });
}
}
}
I have read up on http://www.breezejs.com/documentation/web-api-controller#note01 and http://www.breezejs.com/documentation/web-api-routing but it seems that it's something to do with the way DNN registers the routes. Is there anyway to do this without using BreezeWebApiConfig.cs?
My controller code has the BreezeController attribute. (When I do connect the sample client to it I do get a list of items - it just does not filter, so I think it is something to with the OData Action filters. How can I debug where the problem is?
Update 1)
Here is the metadata:
http://www.ftter.com/desktopmodules/framework/api/dare/metadata
The GetUsers method:
http://www.ftter.com/desktopmodules/framework/api/dare/getusers
and the GetUsers method trying to filter by UserID (which doesn't work, which is the issue)
http://www.ftter.com/desktopmodules/framework/api/dare/getusers?=UserID%20eq%204
http://www.ftter.com/desktopmodules/framework/api/dare/GetUsersWithoutCors?=UserID%20eq%204 (this returns IQueryable)
Here is the controller:
[BreezeController]
public class DareController : DnnApiController
{
private readonly EFContextProvider<FrameworkContext> contextProvider = new EFContextProvider<FrameworkContext>();
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage Metadata()
{
var response = Request.CreateResponse(HttpStatusCode.OK, contextProvider.Metadata());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetUsers()
{
var userInfoController = new UserInfoController();
var response = Request.CreateResponse(HttpStatusCode.OK, userInfoController.GetUsers());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public IQueryable<User> GetUsersWithoutCors()
{
return contextProvider.Context.Users;
}
}
The routing is not really a Breeze issue. How your server routes requests to your controller is up to you. What we do out-of-the-box is just one way among innumerable many.
You have the [BreezeController] attribute on your controller yes? Can you put a sample endpoint up where we could hit it. Might get some clues from that. Also post the controller. A tiny example should do ... something returning metadata and one method returning IQueryable.
Update 25 Jun 2013
I think you've discovered a bug in the way our [BreezeController] discovers methods returning IQueryable<T>.
The [BreezeController] attribute scans your Web API controller methods and (in effect) applies the [BreezeQueryable] attribute to methods returning IQueryable<T>.
[BreezeQueryable] is an extension of the Web API's [Queryable] that adds support for $select, $expand, and nested $orderby ... all missing from the current [Queryable].
I see now that your GetUsers() method returns HttpResponseMessage rather than IQueryable<User>. Let's assume that the userInfoController.GetUsers() method inside your method returns IQueryable<User>. Otherwise, the OData query parameters will not apply and we'll have to take this in a different direction. Moving along ...
I checked with v.1.3.6 of the Breeze.WebApi.dll and it does not detect that the HttpResponseMessage is wrapping IQueryable<T>. Therefore, it does not apply the client's OData query criteria (or any other OData modifiers for that matter). This shortcoming (in my opinion) is a bug. The following should be equivalent implementations:
[HttpGet]
public IQueryable<TodoItem> Todos() {
return _repository.Todos;
}
[HttpGet]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
The second, "wrapped" method does not respect the OData query parameters.
Fortunately, there is a workaround until we get this fixed. Just add the [BreezeQueryable] attribute explicitly ... as in:
[HttpGet]
[BreezeQueryable]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
I confirmed that this approach does work.
Thanks for finding this.
Use OData query syntax
A colleague also noticed that your query URL does not use the OData query syntax. You wrote:
... /todos?=DareId%20eq%204
when it should be
... /todos/?$filter=DareId%20eq%204
Make sure you use ?$filter=

Resources