I asked a similar question a few days ago, but I've got an issue with another controller now and for the life of me I can't figure out why this is returning a 404.
API Controller...
[Route("api/[controller]")]
[ApiController]
public class FilesController
{
private readonly IFilesService _filesService;
public FilesController(IFilesService filesService)
{
_filesService = filesService;
}
[HttpGet("{id}")]
public IEnumerable<SupportFile> GetFiles(int id) {
return _filesService.GetFiles(id);
}
[HttpGet("DownloadFile/{fileName}")]
public async Task<FileStreamResult> DownloadFile(string fileName)
{
return await _filesService.DownloadFile(fileName);
}
}
Program.cs...
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<DapperContext>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ISupportService, SupportService>();
builder.Services.AddScoped<IFilesService, FilesService>();
builder.Services.AddControllersWithViews();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapControllers();
app.UseRouting();
app.MapDefaultControllerRoute();
app.MapFallbackToFile("index.html"); ;
app.Run();
Proxy.conf.js...
const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:19229';
const PROXY_CONFIG = [
{
context: [
"/api/*"
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}
}
]
module.exports = PROXY_CONFIG;
Another api controller that works fine...
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public User ModifyUser(AzureUser user)
{
return _userService.ModifyUser(user);
}
[HttpGet]
public IEnumerable<User> GetUsers()
{
return _userService.GetUsers();
}
}
But no matter what I do...
https://localhost:44427/api/files/123 returns a 404.
https://localhost:44427/api/files/DownloadFile/test.csv returns a 404.
Other API controller methods work, so the port is correct.
The other api controllers only have one GET though, whenever I try to add multiple GETs using attribute routing, they both just end of returning a 404.
Solved this.
It was the proxy.conf.js.
I had to change "/api/*" to "/api/**" in the context array, ** seems to be a catch all.
Your controller should inherit the class ControllerBase.
Instead of this:
public class FilesController
Try this:
public class FilesController : ControllerBase
Have you tried debugging?
Does a file with the ID 123 or name test.csv actually exist?
https://localhost:44427/api/files/123
https://localhost:44427/api/files/DownloadFile/test.csv
If not then then the service methods may return null which will lead to a 404 by default.
Related
I have created the following PersonController.cs:
using Microsoft.AspNetCore.Mvc;
using PersonApi.Models;
namespace donau_lead_generator.Controllers;
[ApiController]
[Route("[controller]")]
public class PersonController : ControllerBase
{
private readonly ILogger<PersonController> _logger;
public PersonController(ILogger<PersonController> logger)
{
_logger = logger;
}
[HttpPost("addData")]
public Task<ActionResult<Person>> Post(Person person)
{
Console.WriteLine("I am here");
return null;
}
And the following service:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { Person } from './person';
#Injectable()
export class HomeService {
constructor(
private http: HttpClient) {
}
/** POST: add a new user to the database */
addUser(user: Person): Observable<Person> {
return this.http.post<Person>("person/addData", user);
}
}
Which is called in the component like this:
this.homeService.addUser(newUser).subscribe(user => {console.warn(user)
}, error => console.error(error)); //maybe create Person
I know that the return value etc. is not correct yet, but the main problem is that the endpoint is not recognized:
My Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");;
app.Run();
If I add the method to the pre generated (weatherforecast) endpoint everything works.
My folder structure:
EDIT
I changed it to, as requested in an answer:
[HttpPost("addData")]
public ActionResult<Person> Post(Person person)
{
Console.WriteLine("I am here");
return Ok(person);
}
However, it still throws the same 404 not found error.
I finally found the problem.
The generated project via the dotnet cli, configures a proxy.conf.js where it specifies which urls are redirected to the server and /weatherforecast is the only url allowed. Thats why the endpoint added in /weatherforecast worked and in an extra controller file not.
To make it work for other endpoints add person in proxy.conf.js:
const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:42175';
const PROXY_CONFIG = [
{
context: [
"/weatherforecast",
"/person"
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}
}
]
module.exports = PROXY_CONFIG;
Replace this:
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
to this: app.MapControllers();
And your url will be like: https://localhost:44416/Person/addData
Note: When using the [Route("[controller]")] attribute with the "[controller]" parameter, in the URL the controller name begins with an uppercase letter
Update
Also try to replace this builder.Services.AddControllersWithViews();
to this: builder.Services.AddControllers(); because you don't use controller with views
This is because the ActionResult types represent various HTTP status codes.
You have an Task<ActionResult<Person>> endpoint return type, but you are trying to return null with no defined code status, so it needs to be wrapped with Ok() or NotFound() methods, etc.
[HttpPost("addData")]
public ActionResult<Person> Post(Person person)
{
Console.WriteLine("I am here");
return OK(person);
}
Good afternoon,
I'm having some trouble with endpoint routing in my web API using attribute routing and the ASP.NET core routing middleware.
I have an API controller that looks roughly like the following:
public class UsersController : ControllerBase
{
[HttpGet]
[Route("v1/users/{id}", Name = nameof(GetUser), Order = 1)]
public async Task<ActionResult> GetUser([FromQuery(Name = "id")] string userGuid)
{
// Implementation omitted.
}
[HttpGet]
[Route("v1/users/me", Name = nameof(GetCurrentUser), Order = 0)]
public async Task<ActionResult> GetCurrentUser()
{
// Implementation omitted.
}
}
I am trying to configure the endpoint routing so that requests for 'v1/users/me' are routed to the 'GetCurrentUser()' method, while requests matching the template 'v1/users/{id}' (where {id} != me) are routed to the 'GetUser()' method. I was hoping that this could be solved by placing the 'v1/users/me' endpoint before the other endpoint in the endpoint order, but it seems the order parameter isn't respected by the routing middleware. I've also tried explicitly mapping the 'v1/users/me' endpoint before mapping the remaining endpoints, but this doesn't seem to work either.
Here is the current startup configuration:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseResponseCompression();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// Explicit mapping of the GetCurrentUser endpoint - doesn't seem to do anything.
// endpoints.MapControllerRoute("v1/users/me", "Users/GetCurrentUser");
endpoints.MapControllers();
}
}
Is this possible to achieve with attribute routing, and if so, what am I missing?
Thanks!
This should already work fine if you just leave the defaults like this:
[HttpGet("v1/users/{id}")]
public async Task<ActionResult> GetUser(string id)
{
return Ok(new { id = id });
}
[HttpGet("v1/users/me")]
public async Task<ActionResult> GetCurrentUser()
{
return Ok(new { id = "current" });
}
With attribute routing, routes that contain constant parts are favored over routes that contain a route variable at the same location. So v1/users/me is ranked higher than v1/users/{id} with id = "me", so you should see the GetCurrentUser run when you access that route. This is regardless of method ordering within your controller.
The issue was with the annotation of the API endpoint methods.
I mistakenly marked the parameter in the GetUser(string id) endpoint with the [FromQuery] attribute rather than the [FromRoute].
The following works as expected:
public class UsersController : ControllerBase
{
[HttpGet]
[Route("v1/users/{id}", Name = nameof(GetUser))]
public async Task<ActionResult> GetUser([FromRoute(Name = "id")] string userGuid)
{
// Changed from [FromQuery(Name = "id")] to [FromRoute(Name = "id")]
// Implementation omitted.
}
[HttpGet]
[Route("v1/users/me", Name = nameof(GetCurrentUser))]
public async Task<ActionResult> GetCurrentUser()
{
// Implementation omitted.
}
}
I have used scaffolding to create an API controller.
This is the test method I have added in there:
namespace MyApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
[HttpPost]
public JsonResult VerifyIsLoggedIn()
{
Dictionary<string, bool> result = new Dictionary<string, bool> { { "Authenticated", true} };
return new JsonResult(result);
}
}
}
My Program.cs looks like so:
namespace MyApp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
I run the app, get the login screen, manage to log in successfully, but then when I go to the URL below, I get an error stating "No webpage was found for the web address:"
https://localhost:12345/api/Authentication/VerifyIsLoggedIn
Seems like I have to make some changes to Program.cs, but everything I have tried hasn't been of any success. How do I resolve this?
Shown URL
https://localhost:12345/api/Authentication/VerifyIsLoggedIn
does not match the attribute route template for the shown controller action (see comments in code)
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase {
//POST api/Authentication
[HttpPost]
public IActionResult VerifyIsLoggedIn() {
var result = new { Authenticated = true };
return Ok(result);
}
}
Also if you try to view the URL in a browser it will default to HTTP GET while the controller can only serve HTTP POST requests.
You would need to update the HTTP Verb used and the route template
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase {
//GET api/Authentication/VerifyIsLoggedIn
[HttpGet("[action]")]
public IActionResult VerifyIsLoggedIn() {
var result = new { Authenticated = true };
return Ok(result);
}
}
Reference Routing to controller actions in ASP.NET Core
Reference Routing in ASP.NET Core
Am facing the same issue in my case when creating new API project uncheck the configure for HTTPS box.
Issue resolved in my case
I have a project MyProject.Api with following structure:
Controllers/
- Api/
- UsersController.cs
- HomeController.cs
Startup.cs
where HomeController.cs looks like this:
namespace MyProject.Api.Controllers
{
public class HomeController : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
public HomeController(IHostingEnvironment hostingEnv) {...}
[HttpGet]
public async Task<IActionResult> Index() {...}
[HttpGet("sitemap.xml")]
public IActionResult SiteMap() {...}
[HttpGet("error")]
public IActionResult Error() {...}
}
}
and UsersController.cs looks like this:
namespace MyProject.Api.Controllers.Api
{
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly ApiHelper<UsersController> _apiHelper;
private readonly IUserService _userService;
public UsersController(ILogger<UsersController> logger, IUserService userService) {...}
[HttpPost("login")]
public async Task<JsonResult> Login([FromBody] LoginRequest request) {...}
[HttpPost("register")]
public async Task<JsonResult> Register([FromBody] RegisterRequest request) {...}
[HttpGet("renew")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<JsonResult> Renew() {...}
}
}
and Startup.cs:
namespace MyProjet.Api
{
public class Startup
{
private IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) {...}
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(c => c.SwaggerDoc("v1", new Info {Title = "Web Api Docs", Version = "v1"}));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase), builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
"spa-fallback",
new {controller = "Home", action = "Index"});
});
});
}
}
}
when I load /swagger the UI loads successfully, but with following error:
Fetch error
Internal Server Error /swagger/v1/swagger.json
and with this error on the server side
System.NotSupportedException: Ambiguous HTTP method for action - WebEssentials.AspNetCore.Pwa.PwaController.ServiceWorkerAsync (WebEssentials.AspNetCore.Pwa). Actions require an explicit HttpMethod binding for Swagger 2.0
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreatePathItem(IEnumerable`1 apiDescriptions, ISchemaRegistry schemaRegistry)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath, String[] schemes)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
but all methods have unique route, unique name and their HTTP methods are explicitly bound. I've tried adding [Route("")] to the HomeController.cs, but that did not work either.
What am I doing wrong?
As #devNull said, the mistake is not in my code but in WebEssentials.AspNetCore.Pwa.PwaController.ServiceWorkerAsync.
UPDATE:
My pull request with fix (Added explicit HttpMethod bindings) was now merged to WebEssentials.AspNetCore.ServiceWorker repository and will hopefully be available in next release on NuGet with version newer than 1.0.59
OLD SOLUTION:
I found the following solution posted here by Gandii.
Create somewhere class ApiExplorerIgnores with following content
public class ApiExplorerIgnores : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.Controller.ControllerName.Equals("Pwa"))
action.ApiExplorer.IsVisible = false;
}
}
Add following code to method ConfigureServices in Startup.cs
services.AddMvc(c => c.Conventions.Add(new ApiExplorerIgnores()))
This should hide the PwaController from ApiExplorer which is used by Swashbuckle.
Had the same error but without any third party library involved.
Problem in my code was, that there where public methods in my controller base class being used by the controller but not to be exposed by the controller.
Example:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class TestController : GenericController<TestClass>
{
public TestController() : base()
{
}
}
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class GenericController<T1>
{
public GenericController()
{
// ...
}
[HttpGet]
public async Task<ActionResult<IEnumerable<T1>>> Get()
{
// ...
}
public void DoStuff()
{
// ...
}
}
Changing the access modifier of DoStuff to internal solved the problem.
Had the same error but it was different solution. When merging master to my branch, I deleted [HttpGet] in my controller above the function that was written in the debug console. Hope that my experience helps someone
my issue was there was no [HttpGet] above my method for it to actually get the api that i wanted
Swagger (at least v3.0 and below) will fail if Http action method has a parameter. So, use [HttpPost] instead of [HttpPost("somename")].
I am trying to read the appsettings.json file with the strongly type class and pass that as a parameter to controller. However its not working. Here is the code.
appsettings.json file:
{
"AppSettings": {
"ApplicationName": "TestApp"
}
}
AppSettings Class:
public class AppSettings
{
public string ApplicationName { get; set; }
}
Injecting in Startup Class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
Controller:
public class ValuesController : Controller
{
private readonly IOptions<AppSettings> _appsettings;
public ValuesController(IOptions<AppSettings> appsettings)
{
_appsettings = appsettings;
}
[HttpGet]
public string Get()
{
return _appsettings.Options.ApplicationName;
}
}
The startup program is successfully getting executed. However, the controller constructor or default get method is not called.
It is working, If I remove the (IOptions appsettings) from the controller constructor.
What's wrong with my code.
IOptions.Options was renamed to IOptions.Value in beta8. See this question.
Changing your Get action from:
return _appsettings.Options.ApplicationName;
to:
return _appsettings.Value.ApplicationName;
should fix that issue.
UPDATE 3/8/2016
The other issue I see here is that you're calling the Get action the "default" action, but the default routing in ASP.Net Core looks for an Index action on the controller.
You can configure the route in Startup.cs to look for a Get action by default instead of an Index action by modifying the Configure function to include something like this:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Get}/{id?}");
});
The default implmentation uses the template template: "{controller=Home}/{action=Index}/{id?}" which is why it looks for an Index action.
Your other options would be to change your Get function to an Index function, explicitly specify the Get action in the url when you visit the site (ex. http://localhost/Values/Get), or specify the action name for the Get method in your controller like this:
[HttpGet]
[ActionName("Index")]
public string Get()
{
return _appsettings.Value.ApplicationName;
}