How to make parameter required in C# api - c#

so for now it will match both of the actions by the route api/comments, I want the second one to be api/comments?blogId=1, but I don't want it to be api/comments/{blogId}.
//Get api/comments
[HttpGet]
[Route("/")]
public async Task<IActionResult> GetAll()
{
var comments = await _context.Comments.ToListAsync();
if(comments != null)
return Ok(new { status = 200, comments });
return NotFound();
}
//Get api/comments?blogId=1
[HttpGet]
public async Task<IActionResult> GetCommentsBy(int blogId)
{
var allComments = await _context.Comments.ToListAsync();
if (allComments != null)
{
var commentsByBlogId = allComments.Where(c => c.BlogId == blogId);
return Ok(new { status = 200, comments = commentsByBlogId });
}
return NotFound();
}

Routes are unique by looking at there themplate. Even if you're using blogId as query parameter, the two actions are using the same route template which is this api/comments.
To do what you're trying to do is to using only have one action that will return result when you send a blogId or not.
So just add one action Get and the logic should look like below:
[HttpGet]
public async Task<IActionResult> GetComments(int? blogId /* blogId i nullable so it is not required */)
{
// Gets the comments query but don't execute it yet. So no call to ToListAsync() here.
var commentsQuery = _context.Comments;
if (blogId.HasValue)
{
// When you've a blogId set in the query then add a filter to the query.
commentsQuery = commentsQuery.Where(c => c.BlogId == blogId);
}
var comments = await commentsQuery.ToListAsync();
// When the list is empty just send it as it is. the caller should be able to handle the case where the comments list is empty and status code is 200
// You also don't need to set the status code in your responde body. The caller should be able to get the response status first before checking the response body.
return Ok(comments);
}

Related

Best practice to resolve request matched multiple endpoints .NET Core Web api

I have multiple actions on getting a HighScore that require a single integer id. What would be the common best practice to fix the matched multiple endpoints error?
[HttpGet("{highScoreId}")]
public async Task<IActionResult> GetHighScoreById(int highScoreId)
{
var highScore = await _highScoreRepo.GetHighScoreById(highScoreId);
return Ok(highScore);
}
[HttpGet("{gameId}")]
public async Task<IActionResult> GetHighScoresByGame(int gameId)
{
var highScores = await _highScoreRepo.GetHighScoresByGame(gameId);
return Ok(highScores);
}
You can either use property Name an attribute HttpGetAttribute for each endpoint
[HttpGet("{highScoreId}", Name="GetByHighScoreId")]
public async Task<IActionResult> GetHighScoreById(int highScoreId)
{
var highScore = await _highScoreRepo.GetHighScoreById(highScoreId);
return Ok(highScore);
}
[HttpGet("{gameId}", Name="GetByGameId")]
public async Task<IActionResult> GetHighScoresByGame(int gameId)
{
var highScores = await _highScoreRepo.GetHighScoresByGame(gameId);
return Ok(highScores);
}
or make one endpoint and check the parameters for the absence of a value
[HttpGet()]
public async Task<IActionResult> GetHighScore([FromQuery] int? highScoreId = -1, [FromQuery] int? gameId = -1)
{
if (highScoreId != -1)
{
var highScore = await _highScoreRepo.GetHighScoreById(highScoreId);
return Ok(highScore);
}
if (gameId != -1)
{
var highScores = await _highScoreRepo.GetHighScoresByGame(gameId);
return Ok(highScores);
}
return NotFound();
}

How To Check The Task Result In the Controller In ASP.NET Core Web API

I have this in my repository:
public async Task<IEnumerable<CatalogModel>> GetCatalogByName(string _UserId, string _CatalogName)
{
var data = await dbcontext.Catalog.Where(x => x.UserId == _UserId).ToListAsync();
return mapper.Map<IEnumerable<CatalogModel>>(data);
}
And currently, this in my Controller:
[HttpGet]
public IActionResult GetCatalogsByName([FromQuery] string UserId, string CatalogName)
{
var task = repository.Catalog.GetCatalogByName(UserId, CatalogName);
return Ok(task);
}
So right now I am returning Ok(task) all the time. I would like to check if there is data returned from the repository or not so I can also return NotFound(task). I could not seem to figure out how to do that.
You will need to wait for the GetCatalogByName to complete before examining the result.
A simple await will do
[HttpGet]
public IActionResult GetCatalogsByName([FromQuery] string UserId, string CatalogName)
{
var task = await repository.Catalog.GetCatalogByName(UserId, CatalogName);
// check task data before return
return Ok(task);
}
But I strongly recommend you to read more about async/await programming here

Task<IActionResult> is getting the wrong value

[ProducesResponseType(200, Type = typeof(OperationResponse<Plan>))]
[ProducesResponseType(400, Type = typeof(OperationResponse<Plan>))]
[HttpGet("{id}")]
public async Task<IActionResult> Get([FromRoute] string id)
{
string userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace();
Console.WriteLine("stack is " + t);
var plan = await _plansService.GetPlanById(id, userId);
if (plan == null)
return BadRequest(new OperationResponse<string>
{
IsSuccess = false,
Message = "Invalid operation",
});
return Ok(new OperationResponse<Plan>
{
Record = plan,
Message = "Plan retrieved successfully!",
IsSuccess = true,
OperationDate = DateTime.UtcNow
});
}
string id should be of type guid and instead I'm getting the word 'search' instead of id which should be a guid for a particular product. How do I trace the value.... or the mapping?
Your URL /api/plans/search?query=you&page=1 has nothing that could be parsed as a GUID in it. Presumably the route prefix on the controller is /api/plans/ so in declaring id as FromRoute in your action you have a route of /api/plans/{id}. Hence you end up with "search" as your id when calling GET with the URL provided.
You could try the following:
[HttpGet("search")]
public async Task<IActionResult> Get([FromQuery] string id)
Then call it as follows:
GET /api/plans/search?id=<guid_string>&page=1`
id will then be set to a string that can be parsed as a guid.
I fixed it by changing api to
var response = await client.GetProtectedAsync<ProductsCollectionPagingResponse>($"{_baseUrl}/api/plans/query={query}/page={page}");
from
var response = await client.GetProtectedAsync<ProductsCollectionPagingResponse>($"{_baseUrl}/api/plans?query={query}&page={page}");

Post anonymous object via HttpClient

I'm trying to post anonymous object via httpclient, however orderId is null and collection empty when it hits controller.
public async Task<Response> CancelOrderAsync(int orderId, ICollection<int> ids)
{
Response result = null;
using (IHttpClient client = HttpClientFactory.CreateHttpClient())
{
var obj = new {OrderId = orderId, Ids = ids};
string json = JsonConvert.SerializeObject(obj);
HttpContent postContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var response = await client.PostAsync($"{url}/admin/cancel", postContent).ConfigureAwait(false))
{
if (response != null && response.IsSuccessStatusCode)
{
...
}
}
}
return result;
}
// Controller
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel(int orderId, ICollection<int> ids)
{
// order is null, collection empty
...
EDIT:
Changed my controller to this for simplicity
[HttpPost]
[ActionName("cancel")]
public async Task<SimpleResponse> Cancel(int orderId)
Via Postman, i'm posting this body:
{
"orderId": "12345"
}
Still, orderId comes in as 0 (zero) ??
The controller action on the server side will need a concrete type to read the entire body of the request
public class Order {
public int OrderId { get; set; }
public int[] Ids { get; set; }
}
This is primarily because the action can only read from the body once.
Update action to...
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel([FromBody]Order order) {
if(ModelState.IsValid) {
int orderId = order.OrderId;
int[] ids = order.Ids;
//...
}
//...
}
the original code used to send the request in the example will work as is, but as mentioned it can be improved.
The HttpClient can do the serialisation for you. See if
var response = await client.PostAsJsonAsync($"{url}/admin/cancel", obj);
works better. Then you don't need to write the serialisation code yourself.
If you still have a problem, use a tool such as Fiddler to monitor the actual request and see what parameter and values are submitted in the request body, to see if they match what's expected by the endpoint.

web api: action parameter is null

I have a simple web api but when I test it with Postman, the method parameter is null. I've looked and over other questions with similar title but I haven't found an answer.
Here is my controller action:
[HttpPost]
public Member Create([FromBody] string payload)
{
var s = Request.Content.ReadAsStringAsync();
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
Console.WriteLine(payload);
Console.WriteLine(s);
return null;
}
And here is my postman configuration:
In the headers tab I've added content-type to be application/json.
when I'm debugging this, the payload variable is allways null and the string s contains something like
Id = 98, Status = RanToCompletion, Method = "{null}", Result = ""
So what am I doing wrong?
Buda,
HttpContent.ReadAsStringAsync returns a Task<string>, hence the value of the s string.
You have to update your action to make it async:
[HttpPost]
public async Task<Member> Create([FromBody] string payload)
{
var s = await Request.Content.ReadAsStringAsync();
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
Console.WriteLine(payload);
Console.WriteLine(s);
return null;
}
You should wrap your string in model (object):
class Model
{
public string payload {get;set;}
}
[HttpPost]
public async Task<Member> Create([FromBody] Model model) // wrap Member in Task and add async keyword
{
var s = await Request.Content.ReadAsStringAsync(); // add await here
if (model.payload == null)
{
throw new ArgumentNullException(nameof(model.payload));
}
Console.WriteLine(model.payload);
Console.WriteLine(s);
return null;
}
If you don't want to use model, try to send only plain string, like "some payload", do not wrap it in json.

Categories

Resources