How to call api with controller's method - c#

I want to call an ASP.NET Core 2.1.0 Web API with a controller's method.
I tried following but I get an error
Cannot GET /api/remote/NewAc/test1
Code:
[Route("api/remote/{action}")]
//[Route("api/[controller]")]
[ApiController]
public class RemoteController : ControllerBase
{
private readonly MyContext _context;
public RemoteValsController(MyContext context)
{ _context = context; }
[HttpGet]
public async Task<OkObjectResult> NewAc()
{
var r = await _context.TypeOfAccounts.AnyAsync();
return Ok(new { r = true });
}
[HttpGet("{id}")]
public async Task<OkObjectResult> NewAc([FromRoute] string AccountType)
{
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == AccountType.ToUpper());
return Ok(new { r = !r });
}
}
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
I tried both [HttpPost] and [HttpGet] but in vain.

Re-check the defined routes for the controller.
[Route("api/remote/[action]")] //<-- NOTE Token replacement in route templates
[ApiController]
public class RemoteController : ControllerBase {
private readonly MyContext _context;
public RemoteController(MyContext context) {
_context = context;
}
//GET api/remote/NewAc
[HttpGet]
public async Task<IActionResult> NewAc() {
var r = await _context.TypeOfAccounts.AnyAsync();
return Ok(new { r = true });
}
//GET api/remote/NewAc/test1
[HttpGet("{accountType}")]
public async Task<IActionResult> NewAc(string accountType) {
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == accountType.ToUpper());
return Ok(new { r = !r });
}
}
Reference Routing to controller actions in ASP.NET Core

First, mapped routes and attribute routing are an either/or affair. If you have route attributes involved, the route definition in your Startup is not applicable at all.
Second, you can't just throw [FromRoute] in front a param and magically have it in the route. In fact, that attribute isn't necessary at all unless there's some ambiguity about where the param value is actually supposed to come from. If you want it from the route, then it needs to be part of your route template. Simply:
[HttpGet("{id}/{AccountType}")]
public async Task<OkObjectResult> NewAc(string AccountType)
{
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == AccountType.ToUpper());
return Ok(new { r = !r });
}

Related

.NET Core API : No route matches the supplied values

i'm making a Web API and i want to retrieve the object created by the CreateCommand Method.
To do that, I'm using the CreateAtRoute function to call the GetCommandById function ,with the id of the created Command as parameter, but i'm getting the following error:
" System.InvalidOperationException: No route matches the supplied
values."
This is my controller:
[Route("api/commands")]
[ApiController]
public class CommandsController : Controller
{
private readonly ICommanderRepo _repository;
private readonly IMapper _mapper;
public CommandsController(ICommanderRepo repository,IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
[HttpGet]
public ActionResult <IEnumerable<CommandReadDto>> GetAllCommands()
{
var commands = _repository.GetAllCommands();
return Ok(_mapper.Map<IEnumerable<CommandReadDto>>(commands));
}
[HttpGet("{id}")]
public ActionResult <CommandReadDto> GetCommandById(int id)
{
var command = _repository.GetCommandById(id);
if(command != null)
{
return Ok(_mapper.Map<CommandReadDto>(command));
}
return NotFound();
}
[HttpPost]
public ActionResult <CommandReadDto> CreateCommand(CommandCreateDto commandCreateDto)
{
var commandModel = _mapper.Map<Command>(commandCreateDto);
_repository.CreateCommand(commandModel);
_repository.SaveChanges();
var commandReadDto = _mapper.Map<CommandReadDto>(commandModel);
var x = nameof(GetCommandById);
return CreatedAtRoute(nameof(GetCommandById), new { id = commandReadDto.Id }, commandReadDto);
}
I have already tried this (which didn't resolve the problem):
Check if the parameters of both functions match
Added to my Startup.cs : services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false); ( i saw this on a post here in stackoverflow
What might be the problem?
When using CreatedAtRoute, you'll need something like shown below. Please note the addition of a route name, and use of that route name in CreatedAtRoute.
[HttpGet("{id}", Name="GetCommand")]
public ActionResult <CommandReadDto> GetCommandById(int id)
{
... // your code here
}
[HttpPost]
public ActionResult <CommandReadDto> CreateCommand(CommandCreateDto commandCreateDto)
{
... // your code here
return CreatedAtRoute("GetCommand", new { commandReadDto.Id }, commandReadDto);
}
An alternative, is to use CreatedAtAction like shown below. With this approach, a route name is not required.
return CreatedAtAction("GetCommandById", new { commandReadDto.Id }, commandReadDto);
You should derive your API-controllers from ControllerBase instead of Controller. The latter is targeting MVC-controllers.
And I believe you should remove options.SuppressAsyncSuffixInActionNames until you actually need it.

404 when passing two parameters to an api

I´m trying to make a simple api on .net core 3.1, I deleted the wheather thing that comes from default. I put a new starting point but despite many efforts and changes, I still get an 404
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
In properties, start explorer:
api/Servicio/GetServicio/tecnico/Pedro/semanaDelAno/8
Controller
[Route("api/Servicio")]
public class ServicioController : Controller
{
private readonly ApplicationDbContext _context;
public ServicioController(ApplicationDbContext context)
{
_context = context;
}
// POST: api/PostServicio
[HttpPost]
public async Task<ActionResult<Servicio>> PostServicio(Servicio servicio)
{
_context.Servicio.Add(servicio);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetServicio), new { id = servicio.Id }, servicio);
}
// GET: api/GetServicio/5
//[HttpGet("{tecnico}/{semanaDelAno}")]
[HttpGet("GetServicio/{tecnico}/{semanaDelAno}")]
public async Task<ActionResult<Servicio>> GetServicio(string tecnico, int semanaDelAno)
{
var servicio = await _context.Servicio.FirstOrDefaultAsync(i => i.Tecnico == tecnico && i.SemanaDelAno == semanaDelAno);
if (servicio == null)
{
return NotFound();
}
return servicio;
}
Assuming tecnico=Pedro and semanaDelAno=8, you have to use this url
~/api/Servicio/GetServicio/Pedro/8
for GetServicio action:
[HttpGet("GetServicio/{tecnico}/{semanaDelAno}")]
public async Task<ActionResult<Servicio>> GetServicio(string tecnico, int semanaDelAno)
{
.... your code
}

CreatedAtAction results in "No route matches the supplied values"

There are huge numbers of questions about the "No route matches the supplied values" error, but I have not yet found any solutions among the answers :(
Here is my controller:
[ApiVersion("0.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class WidgetsController : ControllerBase
{
private readonly IRepository _repository;
public WidgetsController(IRepository repository)
{
_repository = repository;
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Add([FromBody] AddWidgetRequest request)
{
WidgetDetails details;
try
{
details = request.ToWidgetDetails();
}
catch (AddWidgetRequest.BadRequest e)
{
return BadRequest(e.Message);
}
var id = _repository.AddWidget(details);
return CreatedAtAction(nameof(GetById), new {id = id}, details.WithId(id));
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
if (_repository.TryGetWidget(id, out var details))
{
return Ok(details.WithId(id));
}
else
{
return NotFound();
}
}
}
When POSTing to /api/v0/Widgets, the new entry is added to the database, but HTTP 500 is returned, with message "System.InvalidOperationException: No route matches the supplied values.". My code is almost identical to the example in https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1, I'm at a loss as to what the issue could be.
You need specify the api version in the CreatedAtAction method like below:
public IActionResult Add([FromBody] AddWidgetRequest request,ApiVersion version)
{
return CreatedAtAction(nameof(GetById), new { id = 1, version = version.ToString() }, details.WithId(id));
}

Get userId from JWT on all Controller methods?

I am creating a Core 2.0 Web API project that uses JWT for authentication and authorization. My controller methods that I want to secure are all decorated with the Authorize attribute.
This is working. If I pass the JWT in the Bearer header, I get a 200. If I fail to pass the JWT, I get the 401. All working. In my JWT, I have stored the User ID in the 'UserId' field when authorizing..
var claimsdata = new[] {
new Claim("UserId", user.Id.ToString()),
I then have an extension method:
public static string GetUserId(this IPrincipal user)
{
if (user == null)
return string.Empty;
var identity = (ClaimsIdentity)user.Identity;
IEnumerable<Claim> claims = identity.Claims;
return claims.FirstOrDefault(s => s.Type == "UserId")?.Value;
}
On my controller method, with 'Authorize', I often need the ID of the user. So I call my GetUserId method. This works. However, I am unsure if this is the best way to get the Id from the token.
int.TryParse(User.GetUserId(), out _userId);
I need to use that code on all controllers. I can't do it in the constructor, as .. that's wrong I think.
Am I doing the right thing here?
ControllerBase contains User property that is type of ClaimsPrincipal
You can access user claims by User.Claims and no need for IPrincipal
Create a base controller which contains GetUserId method as protected
public abstract class BaseController : Controller
{
protected int GetUserId()
{
return int.Parse(this.User.Claims.First(i => i.Type == "UserId").Value);
}
}
And all controllers inherit form this, now all controllers can access UserId
Firstly I create IUserProvider interface with IHttpContextAccessor injection to make mocks for these interfaces in unit tests.
public interface IUserProvider
{
string GetUserId();
}
Than implementation is
public class UserProvider : IUserProvider
{
private readonly IHttpContextAccessor _context;
public UserProvider (IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserId()
{
return _context.HttpContext.User.Claims
.First(i => i.Type == ClaimTypes.NameIdentifier).Value;
}
}
So you can use interface IUserProvider in your controller without inheritance
[Authorize]
[ApiController]
public class MyController : ControllerBase
{
private readonly IUserProvider _userProvider;
public MyController(IUserProvider userProvider)
{
_userProvider = userProvider ?? throw new ArgumentNullException(nameof(userProvider ));
}
[HttpGet]
[Route("api/My/Something")]
public async Task<ActionResult> GetSomething()
{
try
{
var userId= _userProvider.GetUserId();
}
}
}
Also you can use
Extension Method
like this
public static long GetUserID(this ClaimsPrincipal User)
{
return long.Parse(User.Claims.First(i => i.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value);
}
and implement in your controller like this
[HttpDelete("DeleteAddress")]
public async Task<IActionResult> DeleteAddress([FromQuery] long AddressID)
{
try
{
long userID = this.User.GetUserID();
await _addressService.Delete(userID, AddressID);
return Ok();
}
catch (Exception err)
{
return Conflict(err.Message);
}
}
I hope it will help you
var authenticatedUser = User.Identities.Select(c => c.Claims).ToArray()[0].ToArray()[0];
var userid = await userManager.FindByNameAsync(authenticatedUser['email']).Id;

Validate ModelState.IsValid globally for all controllers

In my ASP.NET Core Controllers I always check if the ModelState is valid:
[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest("invalid parameters");
}
else
{
return Ok("some data"));
}
}
Is there a way to check validity of the ModelState globally using a filter so I don't have to do this in every API item in every controller again? It would be nice if the action could rely on the modelstate being valid and not needing to check:
[HttpPost("[action]")]
public async Task<IActionResult> DoStuff([FromBody]DoStuffRequest request)
{
return Ok("some data"));
}
As a followup to this: in ASP.NET Core 2.1, there is a controller attribute called [ApiController]. If you include that attribute, it automatically uses a built-in ActionFilter called ModelStateInvalidFilter, meaning any custom action filter that checks ModelState will never be reached since the [ApiController] attribute already registers its own filter.
To suppress the behavior, the current documents give this option:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true; // This is the setting
});
You can use a ActionFilter. It's not globally, but it moves the problem from your method body into an attribute. I realize that it doesn't solve your problem completely, but it might be better than nothing.
public class ModelStateValidationActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
And in your controller:
[HttpPost]
[ModelStateValidationActionFilter]
public IHttpActionResult Post(object model)
{
}
I believe that you can set it on your controller as well. I haven't actually tried that, but according to this it could work.
[ModelStateValidationActionFilter]
public class MyApiController : ApiController
{
}
EDIT:
As #Camilo Terevinto mentioned it is a bit different for Core. Just use this ActionFilter if you want to use Core.
public class ModelStateValidationActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var modelState = context.ModelState;
if (!modelState.IsValid)
context.Result = new ContentResult()
{
Content = "Modelstate not valid",
StatusCode = 400
};
base.OnActionExecuting(context);
}
}
The existing answers so far are for ASP.NET Web API and not for ASP.NET Core. The actual way to do it in ASP.NET Core is:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
And you can register this filter globally in Startup.cs, so this will execute in every single call and you do not have to repeat it in every Action/Controller:
options.Filters.Add(typeof(SampleActionFilter));
See more in the official documentation.
For ASP.NET Core 2.0, to avoid applying attributes to all Controllers or Actions individually;
Define a filter:
namespace Test
{
public sealed class ModelStateCheckFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}
Then in your Startup.cs, add it as filter:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.Filters.Add(new ModelStateCheckFilter());
});
}
Use HandleInvalidModelState
PM> Install-Package HandleInvalidModelState
example
[HttpPost]
[TypeFilter(typeof(HandleInvalidModelWithViewActionFilterAttribute))]
public IHttpActionResult Post(object model)
{}
Besides basic cases scenario (returning view with invalid model) package supports returning Json and Redirection of request.
disclaimer : author of the package
For async methods the filter has to implement IAsyncActionFilter. I have put together this filter, which:
does nothing if the ModelState is valid
OR
sets the Result to BadRequestObjectResult with some details about what failed
logs the ModelState with more details
public class ValidModelStateAsyncActionFilter : IAsyncActionFilter
{
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/loggermessage?view=aspnetcore-2.1
private static readonly Action<ILogger, IList<(string Key, string ErrorMessage, string ExceptionMessage)>, Exception> ModelStateLoggerAction;
private readonly ILogger<ValidModelStateAsyncActionFilter> logger;
static ValidModelStateAsyncActionFilter()
{
ModelStateLoggerAction = LoggerMessage.Define<IList<(string Key, string ErrorMessage, string ExceptionMessage)>>(LogLevel.Warning, new EventId(1, nameof(ValidModelStateAsyncActionFilter)), "{ModelState}");
}
public ValidModelStateAsyncActionFilter(ILogger<ValidModelStateAsyncActionFilter> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ModelState.IsValid)
await next();
this.LogModelState(context);
context.Result = new BadRequestObjectResult(GetErrorResponse(context));
}
private static ErrorResponse GetErrorResponse(ActionContext context)
{
return new ErrorResponse
{
ErrorType = ErrorTypeEnum.ValidationError,
Message = "The input parematers are invalid.",
Errors = context.ModelState.Values.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
.Where(x => !string.IsNullOrEmpty(x))
.ToList()
};
}
private void LogModelState(ActionContext context)
{
// credit: https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/
var items = from ms in context.ModelState
where ms.Value.Errors.Any()
let fieldKey = ms.Key
let errors = ms.Value.Errors
from error in errors
select (Key: fieldKey, ErrorMessage: error.ErrorMessage, ExceptionMessage: error.Exception.Message);
ModelStateLoggerAction(this.logger, items.ToList(), null);
}
}

Categories