Use MVC, Razor Pages, Blazor, API Controllers, and Minimal APIs in a single ASP.NET Core application

A few years ago I wrote an article explaining how ASP.NET Core 2.0 MVC, Razor Pages, and Web API can coexist in a single web application. ASP.NET Core has added some new members to the family since then. And it's time to rebuild a similar web application that uses ASP.NET Core 6.0 MVC, Razor Pages, Blazor Server, API controllers, and minimal APIs together in a single project. Usually developers create a new ASP.NET Core project based on a particular project template such as MVC or Blazor. But you are not restricted to a single development option. ASP.NET Core offers multiple development options and you can utilize them in a single project depending on your requirement.

In this walkthrough I will start with an empty project and one-by-one add MVC, Razor Pages, Blazor Server, API controller, and minimal API to it. This will help you understand what configuration is necessary and how various files are organized in such situations.

Let's get started by creating a new ASP.NET Core named AspNetCoreAllInOne based on Empty project template.

The newly created project in the Solution Explorer is shown below.

Add ASP.NET Core MVC

First we will add MVC to the project. So, open Program.cs file and add this code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.MapDefaultControllerRoute();

app.Run();

Notice the code marked in bold letters. We call AddControllersWithViews() to register MVC services with the DI container. The UseStaticFiles() call adds the support foe static files (images, JS files etc.) to the app. The UseRouting() and MapDefaultControllerRoute() methods handle the MVC routing used by the app. In this example we are going to use the default /controller/action/id pattern for the routes and hence we didn't specify any other route pattern.

Now add four folders namely Models, Views, Controllers, and wwwroot to the project.

Then add a new class called AppModel to the Models folder using the Add New Item dialog.

Write the following code in the AppModel.cs file:

namespace AspNetCoreAllInOne.Models
{
    public class AppModel
    {
        public string Message { get; set; }
    }
}

The AppModel class has just one property called Message.

Next, add a new controller class named HomeController to the Controllers folder.

Write the following code in the HomeController class.

using Microsoft.AspNetCore.Mvc;
using AspNetCoreAllInOne.Models;


namespace AspNetCoreAllInOne.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            AppModel data = new AppModel()
            { 
                Message = "Hello World!" 
            };
            return View(data);
        }
    }
}

The HomeController class contains Index() action. The Index() action creates an AppModel object, sets its Message property to Hello World! and passes that model object to the Index view.

Create Views > Home folder and add a new Razor view file named Index.cshtml in it. 

Write the following code in the Index.cshtml file

@model AspNetCoreAllInOne.AppModel

<h1>@Model.Message</h1>

Here we simply output the Message property in the browser.

Add a new HTML page named Default.html in the wwwroot folder. We will use this page later in this example. Just keep it empty for the time being.

At this stage your project will look like this:

If you run the application you will get this output:

Add ASP.NET Core Razor Pages

Now that MVC has been added to the project, let's go ahead and add Razor Pages.

Open Program.cs file again and add this code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Notice that we added two more calls - AddRazorPages() and MapRazorPages() - for supporting Razor Pages.

Then add Pages folder under the project root. And add a new Razor Page named Index.cshtml to it.

Open the Index.cshtml.cs page model class and add the following code:

public class IndexModel : PageModel
{
    public AppModel Data = new AppModel();

    public void OnGet()
    {
        Data.Message = "Hello Galaxy!";
    }
}

We create an object of AppModel and set its Message property to Hello Galaxy! in the OnGet() page handler.

Then open the Index.cshtml page and output the Message property as shown below:

@page
@model AspNetCoreAllInOne.Pages.IndexModel

<h1>@Model.Data.Message</h1>

If you run the application you will see this output: 

Note that after adding Razor Pages, the web application's default page got changed from /Home/Index (MVC default) to /Index (Razor Pages default). Of course, you can manually navigate to the desired URL and invoke MVC or Razor Pages as required.

Add Blazor Server

Adding Blazor Server is slightly more complicated than adding MVC and Razor Pages. Let's go ahead and do that.

Open the Program.cs file and add the following code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();

app.MapDefaultControllerRoute();
app.MapRazorPages();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

Notice the code marked in bold letters. We first call AddServerSideBlazor() to register Blazor Server services. We then setup the Blazor  and fallback page using MapBlazorHub() and MapFallbackToPage() methods.

Next, add Blazor's root component - App.razor in the project's root folder.

Write the following code into App.razor.

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView>
            <p>Sorry, there's 
nothing at this address.</p>
            <a href="/Default.html">Go to Default page</a>
        </LayoutView>
    </NotFound>
</Router>

Here we basically setup Blazor's router. Notice the anchor tag marked in bold letters. If the router can't find a URL it will show an error message and will allow the user to go to the Default.html page (we added Default.html page while configuring MVC earlier in this example).

The App.razor component is loaded from the _Host.cshtml file. So, add a new Razor View file named _Host.cshtml in the Pages folder.

Add the following code in the _Host.cshtml file.

@page

@using Microsoft.AspNetCore.Components.Web
@using AspNetCoreAllInOne
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, 
initial-scale=1.0" />
    <base href="~/" />
    <component type="typeof(HeadOutlet)" 
render-mode="ServerPrerendered" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />
    <script src="_framework/blazor.server.js"></script>
</body>
</html>

Notice the code marked in bold letters. We render the App.razor component using the <component> syntax. We also add a <script> reference to blazor.server.js just before the </body> tag.

Next, we will add two simple Razor Components. First, create a new folder called Shared under the project root. And add a new razor component named WelcomeMessage.razor in it.

Write the following code inside WelcomeMessage component:

@code{

    [Parameter]
    public string Message { get; set; }

}

<h1>@Message</h1>

As you can see, there is a Message parameter property that will be used by a parent component to specify a message. The specified message is simply outputted on the page.

The WelcomeMessage.razor component is used by Index.razor component. So, add Index.razor component in the Pages folder. You already added the Pages folder while creating the Index Razor Page earlier in this example.

Write the following code in the Index.razor file.

@page "/"
@page "/IndexBlazor"
@using AspNetCoreAllInOne.Shared

<WelcomeMessage Message="Hello Universe!" /> 

We used the WelcomeMessage component and specify the Message to be Hello Universe!. Notice that we have also used @page directive to specify a route /IndexBlazor. This way the Index component can be accessed at /IndexBlazor. We already have Index.cshtml file in the Pages folder. Therefore, we can't assign /Index route to the newly added razor component.

These two components are shown in the Solution Explorer below:

Run the application and navigate to /IndexBlazor. You should get the following output.

At this stage the Razor Pages default page is displayed in the browser. What if we want to male our Blazor app as the default. Let's do that.

Rename Index.cshtml to IndexRazorPages.cshtml. And modify the _Host.cshtml as shown below:

@page "/"

@using Microsoft.AspNetCore.Components.Web
@using AspNetCoreAllInOne
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, 
initial-scale=1.0" />
    <base href="~/" />
    <component type="typeof(HeadOutlet)" 
render-mode="ServerPrerendered" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />
    <script src="_framework/blazor.server.js"></script>
</body>
</html>

Note that the @page directive now specifies the route to be /

If you run the app after making this change it will directly show the output from Index.razor component.

Of course, you can also access the component using /IndexBlazor

Add API controller

Now let's add an API controller to the project. Open Program.cs again and add the following lines of code:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddControllers();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.MapControllers();
app.Run();

Notice the lines marked in bold letters. They are calls to AddControllers() and MapControllers() and will take care of Web API related functionality.

Now add an API controller named ValuesController in the Controllers folder.

Write the following code in ValuesController.cs

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        string[] data = new string[] {
            "Hello World!",
            "Hello Galaxy!",
            "Hello Universe!"
        };
        return Ok(data);
    }
}

As an example we added just one action to the ValuesController. The Get() action is marked with [HttpGet] attribute and simply returns an array of three string values - Hello World!, Hello Galaxy!, and Hello Universe!

Run the application and navigate to /api/Values to invoke the Get() action. Here is the output:

As expected the browser receives an array with three string elements.

Add minimal API

Adding minimal APIs is quite straightforward. Open Program.cs file and add the following MapGet() call just before app.Run() :

app.MapGet("/minimalapi/values", () =>
{
    string[] data = new string[] {
                "Hello World!",
                "Hello Galaxy!",
                "Hello Universe!"
            };
    return Results.Ok(data);
});

As you can seem the MapGet() call specifies the endpoint URL to be /minimalapi/values and the handler function simply returns an array of string values.

Run the app again and navigate to /minimalapi/values. Here is the output:

As you can see, the output is identical to the API controller but we invoked the minimal API using a different endpoint (/minimalapi/values).

Using the Default.html page

We have added Default.html page in the wwwroot folder. Let's use it to include URLs to all the endpoints we created so far.

Open Default.html and add the following code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <h1>This is default page!</h1>

    <h3><a href="/Home/Index">
Go to ASP.NET Core MVC</a></h3>

    <h3><a href="/IndexRazorPages">
Go to ASP.NET Core Razor Pages</a></h3>

    <h3><a href="/IndexBlazor">
Go to ASP.NET Core Blazor</a></h3>

    <h3><a href="/Api/Values">
Go to ASP.NET Core API Controller</a></h3>

    <h3><a href="/MinimalApi/Values">
Go to ASP.NET Core Minimal API</a></h3>

</body>
</html>

This is a simple page containing hyperlinks and looks like this in the browser:

Now open Index.razor component and remove this like:

@page "/"

The Index.razor won't be the default component anymore and you will get this error after running the app.

Click on the Go to Default page link and you will go to the Default.html page.

Click on each of the link and see if you get the correct output.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Kriya and Meditation online course are available here.

Posted On : 06 April 2022


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio