Gummibear
Gummibear

Reputation: 411

Razor pages and webapi in the same project

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

Answers (4)

Greg Sipes
Greg Sipes

Reputation: 703

For .Net 8 Razor projects you need to add:

builder.Services.AddControllers();

And also:

app.MapControllers();

Upvotes: 1

Waiseman
Waiseman

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:

  • JWT Token auth to secure webapi and
  • cookie auth web pages.

To add Web api go in your controller section and

  • create a folder named Api
  • and a new controller inside it e.g 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);
        }
    }
}

Startup Configuration

First of you have to add authentication schemes configuration in Startup.cs You have to add both cookie and jwt token configurations

  • But you can select one as default scheme.
  • In this case we have to select cookie scheme as default which will be applied to all without specifying explicitly.
  • To use jwt scheme on webapi we have to specify explicitly.

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

Lion
Lion

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

Ryan
Ryan

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

Related Questions