Reputation: 411
I created a web app (razor pages) in .net core 3.0. Then I added an api controller to it (both from templates, just few clicks). When I run app, razor page works, but api call returns 404. Where is the problem and how can I make it work?
Upvotes: 39
Views: 32196
Reputation: 703
For .Net 8 Razor projects you need to add:
builder.Services.AddControllers();
And also:
app.MapControllers();
Upvotes: 1
Reputation: 37
Add WebApi to your existing dot net core 2 razor pages app and configure authentication schemes. If you are planning to add webapi to your .net web application then you will be required to configure two authentication schemes for your app:
To add Web api go in your controller section and
Api
OrderController
After adding controller you have to specify the authentication scheme e.g JWT and route path prefix e.g “api/”
for all api request calls.
Controller Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ApplicationCore.Entities.OrderAggregate;
using Infrastructure.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace WebRazorPages.Controllers.Api
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Produces("application/json")]
[Route("api/Orders")]
public class OrdersController : Controller
{
private readonly ProductContext _context;
public OrdersController(ProductContext context)
{
_context = context;
}
// GET: api/OrdersApi
[HttpGet]
public IEnumerable<Order> GetOrders()
{
return _context.Orders;
}
// GET: api/OrdersApi/5
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var order = await _context.Orders.SingleOrDefaultAsync(m
=> m.Id == id);
if (order == null)
{
return NotFound();
}
return Ok(order);
}
// PUT: api/OrdersApi/5
[HttpPut("{id}")]
public async Task<IActionResult> PutOrder([FromRoute] int id
, [FromBody] Order order)
{
if (!ModelState.IsValid){
return BadRequest(ModelState);
}
if (id != order.Id){
return BadRequest();
}
_context.Entry(order).State = EntityState.Modified;
try{
await _context.SaveChangesAsync();
}catch (DbUpdateConcurrencyException){
if (!OrderExists(id)){
return NotFound();
} else {
throw;
}
}
return NoContent();
}
// POST: api/OrdersApi
[HttpPost]
public async Task<IActionResult> PostOrder([FromBody] Order order)
{
if (!ModelState.IsValid){
return BadRequest(ModelState);
}
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return CreatedAtAction("GetOrder", new { id = order.Id }, order);
}
// DELETE: api/OrdersApi/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteOrder([FromRoute] int id)
{
if (!ModelState.IsValid){
return BadRequest(ModelState);
}
var order = await _context.Orders.SingleOrDefaultAsync(m => m.Id == id);
if (order == null){
return NotFound();
}
_context.Orders.Remove(order);
await _context.SaveChangesAsync();
return Ok(order);
}
private bool OrderExists(int id)
{
return _context.Orders.Any(e => e.Id == id);
}
}
}
First of you have to add authentication schemes configuration in Startup.cs
You have to add both cookie and jwt token configurations
Add identity
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ProductContext>()
.AddDefaultTokenProviders();
Configure Cookie
services.ConfigureApplicationCookie(options => {
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.LoginPath = "/Account/Signin";
options.LogoutPath = "/Account/Signout";
});
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["jwt:issuer"],
ValidAudience = Configuration["jwt:issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["jwt:key"]))
};
});
services.Configure<JwtOptions>(Configuration.GetSection("jwt"));
Upvotes: 2
Reputation: 17898
In addition to the answer of @Ryan I had to add a default route with the controller/action pattern. Otherwise no controller was reachable, until I set a [Route("example")]
decorator over it. Since I prefer to generate pattern-based routing like in MVC, I defined the default route in Startup.Configure
like this:
app.UseEndpoints(endpoints => {
endpoints.MapRazorPages();
endpoints.MapControllerRoute("default", "api/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
});
Having a controller called CommunityController
, you now reach the index action at /api/community/index
or just use the short form /api/community
cause index is defined as default action in the route.
Additionally, it's still required to add the controllers component in the ConfigureServices
method as shown by @Ryan:
public void ConfigureServices(IServiceCollection services) {
services.AddRazorPages();
services.AddControllers();
// ...
}
Tested with ASP.NET Core 3.1 Razor pages.
Upvotes: 13
Reputation: 20116
You need to configure your startup to support web api and attribute routing.
services.AddControllers()
adds support for controllers and API-related features, but not views or pages. Refer to MVC service registration.
Add endpoints.MapControllers
if the app uses attribute routing. Refer to Migrate MVC controllers.
Combine razor pages and api like:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
});
services.AddRazorPages()
.AddNewtonsoftJson();
services.AddControllers()
.AddNewtonsoftJson();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//other middlewares
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
Upvotes: 55