Using ASP.NET Core Controllers and Razor Pages from a separate shared project or assembly

This post shows how to use shared projects or shared assemblies for ASP.NET Core API Controllers or ASP.NET Core Razor Pages. Sometimes shared logic for different ASP.NET Web API or Web App projects can be implemented in a shared project. The shared project controllers, Razor Pages, services can be referenced and used in the host web application.

Code: https://github.com/damienbod/SharedAspNetCore

History

2023-05-28 Updated to .NET 7

Using ASP.NET Core Controllers from a shared project

Using shared API Controllers in ASP.NET Core is fairly simple. To set this up, a .NET 5 class library was created to implement the shared Controllers for the ASP.NET Core API. The FrameworkReference Microsoft.AspNetCore.App is required and the Swashbuckle.AspNetCore Nuget package was added for the API definitions in the Controller.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
  </ItemGroup>
</Project>

The shared Controller was implemented using the ControllerBase class and the ApiController attribute. The controller uses a scoped service to get the data.

using Microsoft.AspNetCore.Mvc;
using SharedApi.Services;

namespace SharedApi.Controllers;

[ApiController]
[Route("[controller]")]
public class SharedApiController : ControllerBase
{
    private readonly SomeSharedService _someSharedService;

    public SharedApiController(SomeSharedService someSharedService)
    {
        _someSharedService = someSharedService;
    }

    [HttpGet]
    public ActionResult<string> Get()
    { 
        return Ok(_someSharedService.GetData());
    }
}

The SharedApiExtensions class is used to add all services to the IoC for the shared project. The extension method just adds a scoped service SomeSharedService.

using Microsoft.Extensions.DependencyInjection;
using SharedApi.Services;

namespace SharedApi;

public static class SharedApiExtensions
{
    public static IServiceCollection AddSharedServices(this IServiceCollection services)
    {
        services.AddScoped<SomeSharedService>();
        return services;
    }
}

The project was then added to the host Web API project as a project reference.

The ConfigureServices method of the host web application is used to call the AddSharedServices extension method and add the services to the IoC.

public void ConfigureServices(IServiceCollection services)
{

	services.AddControllers();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new OpenApiInfo 
		{ Title = "AspNetCoreApi", Version = "v1" });
	});

	services.AddSharedServices();
}

When the application is started, the Controller from the shared project can be used. The Controller is discovered and no further code configuration is required which is pretty cool.

Using Razor Pages from a Shared Project

Using Razor Pages from a shared project is slightly different. You can create a Razor class library for the shared UI elements. This is a project of type Microsoft.NETSdk.Razor. The project again includes the FrameworkReference Microsoft.AspNetCore.App.

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
  
</Project>

When creating a Razor class library for Razor Pages in Visual Studio, you must check the checkbox Support pages and views.

The shared Razor Pages can be added as required inside the shared project. The example Razor Page uses a service to get the data.

using Microsoft.AspNetCore.Mvc.RazorPages;
using SharedRazorPages.Services;

namespace SharedRazorPages.MyFeature.Pages;

public class Page1Model : PageModel
{
    private readonly SomeSharedPageService _someSharedPageService;

    public Page1Model(SomeSharedPageService someSharedPageService)
    {
        _someSharedPageService = someSharedPageService;
    }

    public List<string> Data = new();

    public void OnGet()
    {
        Data = _someSharedPageService.GetData();
    }
}

A _ViewStart.cshtml is required so that the default layout page can be used or a specific layout for the Razor Pages inside thed shared project.

@{
    Layout = "_Layout";
}

The SharedRazorPagesExtensions is used to register all the services required inside the shared project. The static class uses just one single scoped service.

using Microsoft.Extensions.DependencyInjection;
using SharedRazorPages.Services;

namespace SharedRazorPages;

public static class SharedRazorPagesExtensions
{
    public static IServiceCollection AddSharedRazorPagesServices(this IServiceCollection services)
    {
        services.AddScoped<SomeSharedPageService>();
        return services;
    }
}  

The AddSharedRazorPagesServices extension method can then be used in the host ASP.NET Core Razor Page application to add the services and the UI items from the shared project can be used.

public void ConfigureServices(IServiceCollection services)
{
	services.AddRazorPages();

	services.AddSharedRazorPagesServices();
}

When the application is run, the view can use either Razor Pages from the shared project, or it’s own.

ASP.NET Core supports other UI elements like static files, typescript and so on which can also be implemented inside a shared assembly, project. See the Microsoft docs for details.

Links:

https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection

https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts

4 comments

  1. […] Using ASP.NET Core Controllers and Razor Pages from a separate shared project or assembly (Damien Bowden) […]

  2. […] Using ASP.NET Core Controllers and Razor Pages from a separate shared project or assembly – Damien Bowden […]

  3. Tomasz Malinowski · · Reply

    Great article. I’ve been struggling with a similar problem – my project was divided into different modules in separate assemblies, and I wanted each module to have its own assembly with its relevant RazorPages. MSDN wasn’t really helpful, but the directions in your post worked like a charm.

  4. The most helpful article I found in few days!!
    It is so simple, clear and straight to the point that i’m wondering why all blogs talk about using the AddApplicationPart extension.

    Since I also stugle before founding your article, I have few questions related to the dependency injection magic:

    Can you explain why you only have to do services.AddScoped in the SharedApi and the associate SharedApiController is automatically added to the web project controllers?
    Is the web project startup services.AddControllers automatically discover all the controllers in that SharedApi?
    Is it because the web project reference the SharedApi project directly in Visual Studio?
    Is it will still working if I move the SharedApi project to a stand alone dll library?

    What if I have multiple shared controllers and I only want to share those I want so not all the controllers in the SharedLibrary are automatically added?

    Thanks a lot for your great article!

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.