Web Api Get Overload Throws Multiple actions were found that match the request - rest

I have an api controller:
[RoutePrefix("api/users")]
[Authorize]
public class UsersController : ApiController
that has two Get methods:
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
Calling http://mysite/api/users?$filter=FirstName eq 'George'
or
Calling http://mysite/api/users
causes the exception Multiple actions were found that match the request.
Commenting out either method will cause the other to work.
Any help would be appreciated.

All the Web API routing is about converting url into controller/action. And its mapping must be unambiguous.
In case that we would have only the first actionGet(ODataQueryOptions<ApplicationUser> options) it would match both urls below:
http://mysite/api/users?$filter=FirstName+eq+'George'
http://mysite/api/users
The first url will be converted into call Get(someODataValue), the second could be Get(null)
The same could be applied to second method Get() without params, because both urls will be converted into parameterless call Get() (OData part will be skipped)
So the solution usually should be in two methods, which are really unique by params. E.g. one is object/refence the second is valueType/int
[HttpGet]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{ ... }
[HttpGet]
public async Task<HttpResponseMessage> Get(int id)
{ ... }

Related

EFCore Include function works in Get endpoint but does not work in Put endpoint

Hi I am working with IdentityServer4 and EFCore 3.1.0 recently and have a very weird issue with the following two pieces of code. The result is that in ApiResourcesGetByIdController, apiResource in GetById function includes the value of scope and secret. However in ApiResourcesUpdateController, result in Update function does not include the value of scope and secret.
The usage of these two endpoints is that, the frontend calls the GetById endpoint to get ApiResource object and makes some modification in frontend. Then the frontend calls Update endpoint to update the object in database.
Can anyone help to see what could be wrong? Thank you
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesGetByIdController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesGetByIdController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpGet]
[Route("")]
public IActionResult GetById([FromQuery]int id)
{
ApiResource apiResource = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(apiResource => apiResource.Id == id);
}
}
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesUpdateController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesUpdateController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpPut]
[Route("")]
public IActionResult Update([FromBody] ApiResource resource )
{
ApiResource result = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(ar => ar.Id == resource.Id);
return Ok()
}
}
Not sure why but I deleted the file and recreated it. Then it works by itself.

HttpGetAttribute doesn't work in core web api

Well known situation. I need two endpoints
GetAll -> api/brands
GetById -> api/brands/1
[ApiController]
[Route("api/[controller]")]
public class BrandsController : ControllerBase
{
private readonly BrandRepository repository;
public BrandsController(BrandRepository repository)
{
this.repository = repository;
}
[HttpGet("{id:int}")]
public async Task<ActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
[HttpGet("")]
public ActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}}
So, I always get into GetAll()
Any ideas? Help, please :)
Is it a correct namespace?
using Microsoft.AspNetCore.Mvc;
for
[HttpGet]
Startup.cs
namespace BackOffice
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseMySql(Configuration.GetConnectionString("local")));
services.AddTransient<BrandRepository, BrandRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
});
app.UseCors();
}
}
}
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
Change your the attribute on your GetAll action to simply [HttpGet] and then change the attribute on your GetById action to [HttpGet("{id}")] .
You can use a constraint to id if need be but in your case I don't see any need for it. Generally you can use constraints when you have multiple actions on the same route but with different parameter types. For example, "api/brands/1" to get by integer ID and then maybe you have another action that is mapped to "api/brands/gucci" that will search for the brand by string name. Then you can use the {id:int} and {id:string} constraints in your route template to define which action to invoke.
Also make sure you use IActionResult when declaring the action return types. You don't want to use the concrete ActionResult type. Code samples below.
For the GetById action :
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
For your GetAll action :
[HttpGet]
public IActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}
This will tell the routing middleware which action to invoke. For actions that you want mapped to the base controller route (i.e. "api/brands"), just use the attribute without an overload. Such as [HttpGet], [HttpPost], [HttpDelete]. For the actions that have a route parameter then you can use [HttpGet("{id}")] and so forth depending on the HTTP method. Don't worry about defining the type of the parameter in the attribute route template. You define the parameter in your action's parameters. For instance:
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
// Code here
return Ok();
}
If you want to map a route to something like "api/brands/designers/2" then you would use a template like [HttpGet("designers/{id}")] to do so. Don't put a "/" before the designers.
Edit : Forgot to mention, make sure your Startup.cs is properly configured for Web API routing. You can read the specifics on the ASP.NET Core 3.1 docs for what all the different options do. If you used the Web API template then it's probably fine but it's worth double checking as improperly configured endpoint routing can cause issues. Make sure you have the following in your Configure method in Startup.cs.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Make sure that app.UseRouting(); is called before app.UseEndpoints();

How to get user info directly at JPA level in rest api

I am using REST api with JPA, and getting the user information in the header section .
For audit purpose need to save the user detail with each request.
How to directly get the user info at JPA level (#Prepersist and #PreUpdate hooks) from rest header.
I don't want to pass the details though service layer
Is there any generic way to do it ?
Note-I am not using spring.
Thanks in advance.
I had the similar problem with spring framework. Following idea may help you.
Create AppContext using ThreadLocal
public class AppContext {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setCurrentUser(String tenant) {
currentUser.set(tenant);
}
public static String getCurrentUser() {
return currentUser.get();
}
public static void clear() {
currentUser.remove();
}
}
Use filter or similar to get user from http header and set to the AppContext
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Your code to extract user info from header
// Build user object and set to the AppContext
AppContext.setCurrentUser(user);
//doFilter
chain.doFilter(httpRequest, response);
}
Use AppContext on the repository. It should available on request scope.
#PrePersist
public void onPrePersist() {
if(AppContext.getCurrentUser() != null){
this.user = AppContext.getCurrentUser();
}
}
}

How to get the url of called method resteasy

I making one Rest Service with Restaeasy (java) that have to return the same URL that was called but with one new string
Example Call service:
Post => mybase/myservice/somewrite with some JSON
| Reponse => mybase/myservice/somewrite/123456
So i want to make the mybase/myservice/somewrite url with one generic logic, because if i put String returnURL="mybase/myservice/somewrite"; and i change for example the name of mybase the reponse will not be good
I want somthing like this
someLogicService(JSON);
id=getId();
URL=getContextCallURL();
return URL+\/+id;
But i dont know if this is possible to do it, and less how to do it
You could also inject an instance of type UriInfo using the annotation Context within your resource, as described below:
#Context
private UriInfo uriInfo;
#POST
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
public Response makeContact(Contact contact) {
String requestUri = uriInfo.getRequestUri();
(...)
}
Hope it helps you,
Thierry
I found the answer to my problem, i put inject with #context the httpRequest to my function and call absolutPath :
#POST
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
public Response makeContact(Contact contact, #Context HttpRequest request) {
return Response.ok().header("location", request.getUri().getAbsolutePath().getPath() + contactService.makeContact(contactJSON)).build();
}

Spring multiple #ModelAttribute methods in the same #Controller

I have a page with two different forms (with two different submits) on Spring MVC 3, and I have a problem with #ModelAttribute methods. When I have two on the same controller, they are not always executed making the model to be NULL.
The code:
#Controller
#RequestMapping(value = "/session/admin/permission/{userId}")
public class PermissionController {
#Autowired
private UserManager userManager;
#ModelAttribute("passwordValidation")
private PasswordValidation getPasswordModel(){
return new PasswordValidation();
}
#ModelAttribute("user")
private User getUserModel(#PathVariable("userId") String userId){
//This is not executed
return userManager.getUser(userId);
}
#ModelAttribute("permissionsAvailable")
private PermissionsAvailable getPermissionsModel(#ModelAttribute("user") User user) {
return new PermissionsAvailable();
}
#RequestMapping(method = RequestMethod.GET)
public String adminPermission(){
return "/security/permission";
}
#RequestMapping(method = RequestMethod.POST, params="changeRoles")
public String modifyPermission(#ModelAttribute("permissionsAvailable") PermissionsAvailable permissions,
HttpServletRequest request, #ModelAttribute("user") User user,
final RedirectAttributes redirectAttributes){
//Modify something
}
#RequestMapping(method = RequestMethod.POST, params="changePassword")
public String modifyPassword(
#ModelAttribute("passwordValidation") PasswordValidation passwordValidation,
#ModelAttribute("user") User user,
HttpServletRequest request, BindingResult bindingResult,
final RedirectAttributes redirectAttributes){
return "newpage";
}
}
Don't know why, sometimes everything goes ok and every method is executed, but sometimes they are not executed.
UPDATE: I have two different controllers with the same problem so it must be an error on Spring or something I'm doing wrong.
Thanks.
The documentation doesn't mention anywhere that it's possible to use #ModelAttribute on an argument to a #ModelAttribute annotated method, like you're doing in your "getPermissionsModel()" method. It's possible that's not supported, since it's not documented as being supported. You might want to try either removing the "#ModelAttribute("user") User user" argument from your "getPermissionsModel()" method, and/or instead try just using one #ModelAttribute method to set all your model attributes:
#ModelAttribute
public void setAttributes(#PathVariable("userId") String userId, Model model) {
model.addAttribute(new PasswordValidation());
model.addAttribute(userManager.getUser(userId));
model.addAttribute(new PermissionsAvailable());
}