OData + Swagger. URL generation - .net-core

In my Net 6 Web-API project I'm using OData and Swagger (it was added automatically when project was created).
It works out of the box, but there is an issue with some URLs generated by Swagger.
Here is my OData controller:
public class UsersController : ODataController
{
// skipped for brevity
[EnableQuery]
public IActionResult Get()
{
return Ok(_dbContextRepo.Select<DbModel.User>());
}
[EnableQuery]
public SingleResult<User> Get([FromODataUri] int key)
{
return SingleResult.Create(_dbContextRepo.Select<User>().Where(u => u.Id == key));
}
}
SwaggerUI output:
When I try to run query that gets entity by Id from Swagger it fails because of wrong url.
By some reason Swagger generates query parameter and URL like on picture above. For OData URL has to be like this (path parameter, https://swagger.io/docs/specification/describing-parameters/):
https://localhost:7250/api/Users/1
In swagger.json parameter described as
Spent all day trying to figure this out. Will appreciate any help.

Found solution myself. I'm using OData 8 and it seems there is no need to mark method parameter as [FromODataUri].
[EnableQuery]
public SingleResult<User> Get(int key)
{
//...
}
Whithout it Swagger generates correct links.

Related

Set custom route using OData 8

Recently I updated to OData 8.0.10. I added this in my Startup.cs file:
services.AddRouting();
services.AddControllers().AddOData(opt =>
opt.AddRouteComponents("odata", GetEdmModel()).Filter().Select().OrderBy().Count());
where
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Project>("Project");
return builder.GetEdmModel();
}
I have this small controller
[Route("api/[controller]")]
[ApiController]
public class ProjectController : ControllerBase
{
[HttpGet]
[EnableQuery(PageSize = 20)]
public IQueryable<Project> GetAsync()
{
var projects = _projectRepository.GetAll();
return projects;
}
[HttpGet("{id}", Name = "GetProjectById")]
public async Task<ActionResult> GetAsyncById(long id)
{
var project = await _projectService.GetProjectByIDAsync(id);
return Ok(project);
}
[HttpPatch("{id}", Name = "PatchProjectById")]
public async Task<ActionResult> PatchProject(long id, [FromBody] ProjectPatchDetails projectPatch)
{
var project = await _projectRepository.GetAsync(id);
var updated = await _projectService.UpdateProjectAsync(id, project, projectPatch);
return Ok(updated);
}
}
that has three endpoints, one of them is annotated by [EnableQuery] and the rest aren't. When I access api/project?$count=true&$skip=0&$orderby=CreateDate%20desc, I get a paged info (20 records) but I don't get the #odata.context and #odata.count. If I access /odata/project?$count=true&$skip=0&$orderby=CreateDate%20desc, with odata/ prefix, it gives me #odata.context and #odata.count. I tried changing AddRouteComponents to AddRouteComponents("api", GetEdmModel()) but in this case I get the following error:
"The request matched multiple endpoints. Matches: MyApp.Api.Controllers.ProjectController.GetAsync (MyApp.Api) MyApp.Api.Controllers.ProjectController.GetAsync (MyApp.Api)"
I have multiple questions in this case:
Is there a way to reroute odata to api, make /odata prefix as /api and make it work?
Should I make another controller that will store all OData tagged actions and on this way maybe workaround this as a solution, if possible?
#anthino
Is there a way to reroute odata to api, make /odata prefix as /api and make it work?
if you add 'opt.AddRouteComponents("api", GetEdmModel())', remember to remove
[Route("api/[controller]")] and other attribute routings
Should I make another controller that will store all OData tagged actions and on this way maybe workaround this as a solution, if possible?
Basically, it's better to create two controllers, one for odata, the other for others. In your scenario, you mixed them together. You should be careful about this. You can use 'app.UseODataRouteDebug()' middleware to help you debug.
I think your ProjectController should be inheriting from ODataController not ControllerBase. With the ODataController, you should get the context url and the #odata.count

How to set default versioning in ASP.NET Core 6 Web API for my scenario?

Just realised that my understanding about ASP.NET Core 6 Web API versioning is wrong.
This is my controller:
[ApiVersion("1.0")]
[ApiController]
[Authorize]
public class FundController
{
[MapToApiVersion("1.0")]
[Route("/Fund/v{version:apiVersion}/delta")]
public async Task<List<PortfolioHolding<Holding>>> Delta([FromQuery] Request dataModel)
{
}
}
What I want is to support route /Fund/v1.0/delta and /Fund/delta, when versioning not provided by the consumer (e.g. calling /Fund/delta), the default version will be hit.
So I configured the versioning like this. However, when I call /Fund/delta, I get a http 404 error.
But /Fund/v1.0/delta will hit the correct controller.
What am I doing wrong?
services.AddApiVersioning(option =>
{
option.DefaultApiVersion = new ApiVersion(1, 0);
option.AssumeDefaultVersionWhenUnspecified = true;
option.ReportApiVersions = true;
});
Usually, it's pretty easy to do this that way. The disadvantage of this approach is that you need to manually change the "default" version of API with this attribute
The problem is that you have not specified the routes in the controller.
You should add the default route as well as the formatted version route. Then you should ensure that your endpoints have the version specified in the MapToApiVersion attribute.
Here is a code sample of what your controller should look like:
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("[controller]")]
[Route("[controller]/v{version:apiVersion}")]
public class FundController : ControllerBase
{
[MapToApiVersion("1.0")]
[Route("delta")]
[HttpGet]
public async Task<List<PortfolioHolding<Holding>>> DeltaV1([FromQuery] Request dataModel)
{
}
[MapToApiVersion("2.0")]
[Route("delta")]
[HttpGet]
public async Task<List<PortfolioHolding<Holding>>> DeltaV2([FromQuery]
Request dataModel)
{
}
}

ASP.NET Core 3.0 Web API - controller method not hit

I am new to ASP.NET Core Web API - this is my controller method:
[HttpPost]
[Route("Createnewlead")]
public IActionResult LeadCreate([FromBody]CRM_Lead Lead)
{
// do stuff
}
This is my json:
{
"RegionID": "1",
"RunningNo": "1633",
"CardName": "Google Pte Limited",
"Telephone": "65748394",
"Mobile": "89349859",
"Fax": "47850555",
"Email": "sre#hotmail.com",
"ROC": "28IO45h44",
"OwnerEmail": "huisan#syspex.com"
}
Please advise me!
Update your route to
[Route("api/[Controller]/Createnewlead")]
Your route is currently set to [Route("Createnewlead")] which will translate to route https://localhost:5001/createnewlead but you are calling https://localhost:5001/api/sap/createnewlead
or alternatively, post to
localhost:5001/Createnewlead
In your example, you are setting a route per action, instead you can also set a route per Controller, for example
[Route("api/[controller]/[action]")]
public class MyController: Controller
{
}
or
[Route("[controller]/[action]")]
public class MyController: Controller
{
}
or
[Route("[controller]")]
public class MyController: Controller
{
}
It all depends on what you want your routing to look like
I could reproduce your problem (400 error and not hit the controller) in asp.net core 3.0 web api using below model(which defines RegionID as int instead of string):
public class CRM_Lead
{
public int RegionID { get; set; }
public string CardName { get; set; }
//other properties
}
It is caused by the default System.Text.Json in asp.net core 3.0.
Solution1:
Change your post json to remove "" on the properties whose types are int
{
"RegionID": 1,//instead of "1"
....
}
Solution2: Keep your above json and use old Newtonsoft.Json in an ASP.NET Core 3.0 project by referencing Json.NET support.
1) Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.0.0
2) Add services.AddControllers().AddNewtonsoftJson(); in startup.cs
Check Ur Routing Attribute and Postman / Browser URL, both are totally different
Your Route attribute has no route "api/sap/Createnewlead" it has only "Createnewlead".
Change ur routing attribute url with ur desired url
I'm using asp.net core 6.0 with Angular and when I follow the documentation for conventional routing with multiple controllers I do not for some reason get the expected behavior- the api call does not hit the endpoint, but when I follow the attribute routing documentation I get the expected behavior.

ASP.NET Core 2.1 CreatedAtRoute Returns no Response

I was searching around but I couldn't find a working answer for my issue. I saw a similar question but we had different results. My issue is I have 2 controllers. The first controller has a POST action that I want to return a CreatedAtRoute to a GET action inside a different controller. I tested the GET action in Postman and it works so my only issue is getting the CreatedAtRoute to work.
My first controller:
[HttpPost]
public async Task<IActionResult> Submit(AssessmentAttemptVM attempt)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
//Do database related stuff
await _context.SaveChangesAsync();
return CreatedAtRoute("GetAssessmentResult", new { id = studentAttempt.Id }, studentAttempt);
}
My second controller:
[HttpGet("{id}", Name = "GetAssessmentResult")]
public async Task<ActionResult<AssessmentResultVM>> GetById(int id)
{
//Get my ViewModel -- This works if I try to access it without using the CreatedAtRoute method
return resultVM;
}
The picture below shows what Postman responds with when I try to Post. I verified that the data gets added to the database so only the CreatedAtRoute method is the only I can think of that isn't making this work for me..
EDIT
Controller Route Attributes:
[ApiController]
[Route("api/view/assessmentresult/")]
public class AssessmentResultsController: ControllerBase
{
[ApiController]
[Route("api/take/assessment")]
public class StudentAssessmentController : ControllerBase
{
I found the cause. The last parameter for CreatedAtRoute and CreatedAtAction required an object similar to the destination controller. It went over my head because I was sending models prior to what I did now which used a ViewModel.
Well That wasn't the main reason I couldn't get a response though. It was because of an execption where the object I'm passing ended up having recursive references because I was sending a model that wasn't properly formatted to be sent over as JSON.
I used this to make it work, taken from the MS Docs site:
CreatedAtAction(String, String, Object, Object) Where the last parameter should be the object you want to the api to send over.
PS: I also didn't notice immediately because when I debugged the project, it didn't crash and had to read the logs. I'm a noob so I really didn't know that it's possible for an exception to occur without the project crashing in debug mode.

404 Not Found when accessing a Web API method

I need this method to return an integer value:
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpPost("ByPayment")]
public int Payment(string accountId, string mount, string shenase)
{
return 21;
}
}
When I go to the following address:
http://localhost:1070/api/values/Payment/ByPayment?accountId=258965&mount=85694&shenase=85456
I get the following error:
What's the problem? And how can I solve it?
I thing you wanted to send Get request with query string parameters.
1. Change the 'HttpPost' to 'HttpGet'
[HttpPost("ByPayment")] to [HttpGet("ByPayment")]
2. Also change your request url, Its not correct.
http://localhost:1070/api/values/Payment/ByPayment?accountId=258965&mount=85694&shenase=85456
to
http://localhost:1070/api/Values/ByPayment?accountId=258965&mount=85694&shenase=85456
Updated code
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpGet("ByPayment")]
public int Payment(string accountId, string mount, string shenase)
{
return 21;
}
}
I suggest please read this tutorial to understand the basic of webapi.
There could be more reasons why you get the 404. But there is one thing that's definitely wrong - you are sending GET requests to a method that's marked with [HttpPost("ByPayment")] (which means it only responds to POST requests.
I don't know what you intended to do but you either have to change it to [HttpGet("ByPayment")] or use a REST client that can make POST requests (e.g. REST Easy.
Other reason could be that your controller has a wrong name. It should be called PaymentController.

Resources