---
title: Migrating ASP.NET MVC 5 projects to ASP.NET Core
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

This page covers the main differences between Kentico Xperience live site projects built using [ASP.NET MVC 5](https://docs.kentico.com/13/developing-websites/starting-with-mvc-development.md) and [ASP.NET Core](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core.md). It is aimed at developers intending to transition existing projects running on MVC 5 to ASP.NET Core.

This page covers:

- fundamental changes between the Xperience MVC 5 and Core development models related to the integration of Xperience into an application, feature development, and extensibility
- differences between the MVC 5 and Core platforms that invalidate some of the information in our MVC 5 development documentation and recommended replacements

Every section contains links to relevant pages within the [Xperience ASP.NET Core development](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core.md) documentation or the official [ASP.NET Core documentation](https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core) from Microsoft with further information and details.

> **Tip:** For a general migration guide for ASP.NET MVC 5 projects covering application logic migration, see Microsoft's [Migrate from ASP.NET to ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/migration/proper-to-2x/) and [Learn to migrate from ASP.NET MVC to ASP.NET Core MVC](https://docs.microsoft.com/en-us/aspnet/core/migration/mvc).

## Table of contents

## Application startup and Xperience integration

In ASP.NET Core, Xperience is a set of middleware components added to the application's [middleware pipeline](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/).

In contrast to MVC 5, where integrating Xperience requires actions across multiple classes (_ApplicationConfig_ to enable system features, _RouteConfig_ to register routes, _Startup.Auth_ for membership integration, and _Global.asax_ for general application configuration), everything is bootstrapped in the application's **Startup** class and its **ConfigureServices** and **Configure** methods.

In the _ConfigureServices_ method, you:

- Add Xperience services to the application's service container (IoC) using **IServiceCollection.AddKentico**.
- [Enable Xperience features](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/starting-with-asp-net-core-development/enabling-xperience-features-in-asp-net-core-applications.md) to use in the project via the **IFeaturesBuilder** delegate.
- Add the framework features required by the system.

```csharp title="Adding Xperience services and enabling features"

public void ConfigureServices(IServiceCollection services)
{
    // Adds Xperience services to the application's service container
    services.AddKentico(features =>
    {
        // Enables Xperience features
        features.UsePageBuilder();
        ...
    });

    // Adds framework features required by Xperience
    services.AddControllersWithViews();
    services.AddAuthentication();
    ...
}

```

In the _Configure_ method you:

- Add Xperience and framework middleware components required by the integration.
- Map Xperience routes.

```csharp title="Configuring the middleware pipeline"

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseStaticFiles();
    ...

    // Adds the Xperience middleware to the pipeline. Internally adds the routing (UseRouting) and session (UseSession) middleware.
    app.UseKentico();
    ...

    app.UseCookiePolicy();
    ...

    app.UseCors();
    ...

    app.UseAuthentication();
    ...

    app.UseEndpoints(endpoints =>
    {
        // Adds system routes such as HTTP handlers and feature-specific routes.
        endpoints.Kentico().MapRoutes();
        ...
    });
}

```

For more information, see [Starting with ASP.NET Core development](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/starting-with-asp-net-core-development.md).

## Dependency injection (IoC) container configuration

ASP.NET Core applications come with an inversion of control (IoC) container [provided by the framework](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection). Xperience uses this container to store all its services. Since the container is available globally, you do not need to register child containers for your services (as described in [Initializing Xperience services with dependency injection](https://docs.kentico.com/13/developing-websites/initializing-xperience-services-with-dependency-injection.md)). All services can be placed directly into the default container.

```csharp title="Adding application services to the IoC container"

using Kentico.Web.Mvc;

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Adds all Xperience services to the service container
    services.AddKentico();
    // Place to register application services
    services.AddSingleton<IMyService, MyService>();
}

```

The default container can be substituted for a supported third-party container if you need more robust functionality. See [Replacing the default service container](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/starting-with-asp-net-core-development.md#replacing-the-default-service-container).

## Request life cycle events

Due to differences between the .NET Framework and .NET Core architecture, only a subset of events from the **CMS.Base.RequestEvents** class are supported for .NET Core applications.

The following table lists available events **in the order they are invoked by the application**:

| Event                 | Event type | Handler parameters                                                                         | Description                                                                                                                                                                                                                            |
| --------------------- | ---------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Prepare               | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Occurs before request processing begins.                                                                                                                                                                                               |
| Begin                 | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Occurs when request processing starts.                                                                                                                                                                                                 |
| PreSendRequestHeaders | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Occurs just before ASP.NET sends HTTP headers to the client.<br>**Note**: Added delegates are invoked during [HttpResponse.OnStarting](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpresponse.onstarting). |
| End                   | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Occurs at the end of request processing.                                                                                                                                                                                               |
| RunEndRequestTasks    | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Allows you to execute background tasks at the end of request processing. Running long tasks in this event's handlers can negatively affect the response time of requests.                                                              |
| Finalize              | Execute    | [EventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.0) | Occurs when the request is finalized. Use to clean up and release any resources used by the request. Running long tasks in this event's handlers can negatively affect the response time of requests.                                  |

## Handling HTTP errors

For MVC 5 applications, we advise handling HTTP errors through IIS. This approach is strongly coupled with the application's hosting server.

For ASP.NET Core, use the approach described in [Handle errors in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling) from the ASP.NET Core documentation. For example:

```csharp title="Example error handling"

if (env.IsDevelopment())
{
    // If the application is running in the Development environment, displays a special page with a detailed breakdown of the error
    app.UseDeveloperExceptionPage();
}
else
{
    // In all other cases, redirects to a URL handled by a conventional controller action method or a Razor page
    // You can access exception details within the handler and serve a relevant page based on the error
    app.UseExceptionHandler("/Error");
}

...

// The 'UseKentico()' middleware registration must be called after the error handlers are registered
app.UseKentico();

```

This approach is independent of the application's hosting environment and comes integrated directly into the framework.

## Making Xperience classes and modules discoverable

For MVC 5 projects, we recommend the **AssemblyInfo** class as the location for the **AssemblyDiscoverable** assembly attribute. The attribute marks assemblies containing [custom implementations](https://docs.kentico.com/13/custom-development.md) (interfaces, providers, etc.), [modules](https://docs.kentico.com/13/custom-development/creating-custom-modules.md), or [generated classes](https://docs.kentico.com/13/developing-websites/generating-classes-for-xperience-objects.md) intended to be consumed by Xperience.

Class libraries targeting the .NET Standard or .NET Core platforms do not have dedicated **AssemblyInfo** classes included in the project by default. Instead, the build process generates an assembly info file directly to the output. However, since the _AssemblyDiscoverable_ attribute provides information about an assembly, its location is not tied to any particular class. You are free to place it anywhere within the class library project.

For example, you can create an empty _AssemblyDiscoverable_ dummy class within the project and place the attribute there:

```csharp title="AssemblyDiscoverable.cs"

using CMS;

[assembly: AssemblyDiscoverable]

```

## Web.config application configuration

ASP.NET Core projects use [configuration providers](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to work with application settings. If your MVC 5 application makes use of [web.config application keys](https://docs.kentico.com/13/configuring-xperience/reference-web-config-application-keys.md) provided by Xperience to modify certain behavior or functionality, migrate these keys to one of the configuration files registered for the Core application (Core web project templates come with an _appsettings.json_ file registered by default).

For example:

```xml title="Example appSettings section in a web.config"

<appSettings>
    <add key="CMSHashStringSalt" value="1c0af037-71f0-4b91-95d3-a818657de1a7" />
    ...
</appSettings>
<connectionStrings>
  <add name="CMSConnectionString" connectionString="..." />
</connectionStrings>

```

Is converted into:

```csharp title="Example configuration file"

{
    "CMSHashStringSalt": "1c0af037-71f0-4b91-95d3-a818657de1a7",
    "ConnectionStrings": {
        "CMSConnectionString": "<value>"
    }
}

```

The database connection string (**CMSConnectionString**) must be nested within a _ConnectionStrings_ object.

## Output caching

Xperience Core sites use and extend native output caching support provided by ASP.NET Core. The MVC 5 approach using the **OutputCache** attribute and **GetVaryByCustomString** implementations (as described on [Caching the output of controller actions](https://docs.kentico.com/13/configuring-xperience/configuring-caching/caching-on-mvc-sites.md#caching-the-output-of-controller-actions) and [Caching the output of personalized content](https://docs.kentico.com/13/configuring-xperience/configuring-caching/caching-the-output-of-personalized-content.md)) is not supported.

Instead, use the built-in [cache Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/cache-tag-helper) to cache page output.

```xml title="cache Tag Helper example"

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<cache priority="High" expires-sliding="@TimeSpan.FromSeconds(60)" vary-by-user="true">
    @* Cached content... *@
</cache>

```

This approach also comes with the advantage of being able to cache only specific parts of the output (known as donut caching) instead of the whole markup returned by controller actions.

If you need to add cache dependencies on Xperience objects, use the [cache-dependency Tag Helper](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/reference-xperience-tag-helpers.md) within sections encapsulated by the _cache_ tag.

```xml title="cache-dependency Tag Helper example"

@* Adds the Tag Helper to the view *@
@addTagHelper  Kentico.Web.Mvc.Caching.CacheDependencyTagHelper, Kentico.Web.Mvc

@{
    // Creates cache dummy keys targetting all user objects in the system and the /Home page
    var userCacheKeys = new[] { "cms.user|all", "node|mysite|/home" };
}

<cache>
    @* Ensures that contents of this cached section are evicted whenever any user in the system or the /Home page is modified *@
    <cache-dependency cache-keys="@userCacheKeys" />

    Time: @DateTime.Now
</cache>

```

Moreover, Xperience Core introduces caching support for [page builder widgets](https://docs.kentico.com/13/developing-websites/page-builder-development/developing-widgets.md). Caching is enabled per widget and caching strategies can be configured individually for each [editable area](https://docs.kentico.com/13/developing-websites/page-builder-development/creating-pages-with-editable-areas.md). The system also contains robust and extensible support for cache personalization.

See [Caching page output in ASP.NET Core applications](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/caching-page-output-in-asp-net-core-applications.md).

## Hosting and deployment

Xperience Core sites retain the dual-application model from ASP.NET MVC 5. However, where the administration application needs to be hosted under Internet Information Services (IIS) servers due to being built using ASP.NET WebForms, the Core live site application is more flexible and can be hosted on any platform supported by the ASP.NET Core framework. Of course, both applications still need to be able to connect to the shared database.

See [Deploying and hosting ASP.NET Core applications](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/deploying-and-hosting-asp-net-core-applications.md) and [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy).

## Page and form builder component development

The page and form builder features in ASP.NET Core contain several key differences related to component development.

### Page builder – widgets and sections

Widgets and sections are implemented as:

- standalone views with optional properties classes (injected via the **ComponentViewModel** model class) – this scheme is identical to MVC 5.
- view components – this scheme serves as a counterpart to widgets and sections using a custom controller derived from the _WidgetController_ class. Instead of using the _Index_ action method to render the default state of a widget, the system uses [view components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) and their **Invoke** or **InvokeAsync** methods (both the synchronous and asynchronous approach is supported).

  When converting controller _Index_ methods to view component _Invoke(Async)_ methods, the method's signature **must**  accept the  **ComponentViewModel**  parameter. For widgets with custom properties, you also need to specify the properties class as the generic parameter.

  ```csharp

  // The signature of a view component's InvokeAsync method for widgets without custom properties
  public async Task<IViewComponentResult> InvokeAsync(ComponentViewModel widgetProperties)

  // The signature of a view component's InvokeAsync method for widgets with custom properties
  public async Task<IViewComponentResult> InvokeAsync(ComponentViewModel<TWidgetPropertiesClass> widgetProperties)

  ```

  Furthermore, the method's view result must specify the full relative path to the view rendered by the view component (the widget's or section's partial view file).

  ```csharp

  return View("~/Components/Widgets/MyWidget/_MyWidget.cshtml", model);

  ```

  For handling POST requests, you need to create a dedicated controller class (together with corresponding routes). The system also provides injectable services that allow you to retrieve the component's properties and the context of the page where the component is rendered in POST actions.

See [Developing widgets](https://docs.kentico.com/13/developing-websites/page-builder-development/developing-widgets.md) and [Developing page builder sections](https://docs.kentico.com/13/developing-websites/page-builder-development/developing-page-builder-sections.md) for details.

### Page builder – selectors

Displaying of items in [selectors](https://docs.kentico.com/13/developing-websites/page-builder-development/selectors-for-page-builder-components.md) is performed asynchronously in ASP.NET Core. Most of the system's selectors handle this internally, with the exception of the **General selector**. When implementing general selector data providers for ASP.NET Core projects, you need to:

- Use the **GetSelectedItemsAsync** method to retrieve and format selected items, instead of the _GetSelectedItems_ method.
- If you use the [ObjectQuery API](https://docs.kentico.com/13/custom-development/retrieving-database-data-using-objectquery-api.md) to retrieve items for the selector, call asynchronous methods to execute (materialize) your queries. For example, **GetEnumerableTypedResultAsync** instead of _GetEnumerableTypedResult_.

### Page builder – page templates

Page templates consist of a view file and a properties class (if required). By default, no controller is necessary. In fact, page templates with a controller cannot be registered.

If your page template needs additional business logic that was previously handled by a controller, for example to

- react to the configuration of the  template's properties
- perform interactions based on the page where the  template is rendered
- execute general business logic not suitable for views (e.g., database operations)

create dedicated service or view component classes and place the logic there. To access the template's properties, use the **ComponentViewModel** model class. For handling POST requests, you need to create a dedicated controller class. The system also provides services that allow you to retrieve the template's properties and the page context in POST actions.

See [Developing page templates](https://docs.kentico.com/13/developing-websites/page-builder-development/developing-page-templates.md) for details.

### Form builder – form sections

Form sections that retrieve their markup via a custom controller's _Index_ action are not supported in ASP.NET Core. Instead, sections can be implemented as [view components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Render the section by implementing the view component's **Invoke** or **InvokeAsync** methods.

See [Developing custom form layouts](https://docs.kentico.com/13/developing-websites/form-builder-development/developing-custom-form-layouts.md) for details.

### Components folder

We recommend storing all files related to a single component in a dedicated folder (e.g., _\~/Components/Widgets/MyComponent_).

The folder can store:

- the main code files (view components, controllers, models, helper classes)
- properties classes
- corresponding views rendered by the component

This structure offers more clarity and a better developer experience than having individual files scattered across the solution.

When transitioning to this structure, you need to explicitly set the **customViewName** property (for basic components not based on a view component) in the corresponding _Register\*_ attributes since the system cannot automatically detect the view to serve based on location conventions.

```csharp title="Setting customViewName"

using Kentico.PageBuilder.Web.Mvc;

[assembly: RegisterWidget("MyProject.Widgets.MyWidget",
                         "Widget Name",
                         typeof(WidgetProperties),
                         customViewName: "~/Components/Widgets/MyWidget/_MyWidget.cshtml")]

```

### Asynchronous extension methods

ASP.NET Core does not support synchronous rendering of views within Razor syntax (.cshtml files). As a result, all system extension methods used by the builder features that render a (partial) view are asynchronous:

- FormZone -> FormZoneAsync
- EditableArea -> EditableAreaAsync
- WidgetZone -> WidgetZoneAsync
- RenderStandaloneWidget
- RenderNestedWidget

These methods need to be prefixed with the _@await_ directive when called within Razor syntax (alternatively, use their corresponding [tag helpers](#tag-helpers-for-builder-extension-methods)).

```csharp title="Example.cshtml"

using Kentico.PageBuilder.Web.Mvc;

@await Html.Kentico().EditableAreaAsync("area1")

```

### Tag helpers for builder extension methods

Most builder extension methods intended to be used in views have corresponding [tag helper alternatives](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/reference-xperience-tag-helpers.md). Using tag helpers removes the need to _@await_ individual \*_Async_ methods and provides an alternative that more neatly fits into HTML-heavy Razor syntax.

```csharp title="Example.cshtml"

@addTagHelper Kentico.Content.Web.Mvc.EditableAreaTagHelper, Kentico.Content.Web.Mvc

<editable-area area-identifier="area1" />

```

## Bundling support for page and form builder assets

In MVC 5 projects, static assets for page and form builder components are gathered from predetermined locations (\*\~/Content/\*folders) and automatically bundled using native [ASP.NET bundling support](https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/bundling-and-minification). The bundles are then linked on all pages where the _Html.Kentico().PageBuilderScripts_ and _Html.Kentico().PageBuilderStyles_ methods are called.

For ASP.NET Core, Xperience provides no default bundling or minification support. The system only provides customizable "pickup points" on the filesystem where it expects individual bundles. The actual implementation of the bundling and minification process itself is left to the developers. This also means that there are no longer any predetermined locations for builder component assets. You can organize the folder structure according to your requirements.

See [Bundling static assets of builder components](https://docs.kentico.com/13/developing-websites/developing-xperience-applications-using-asp-net-core/bundling-static-assets-of-builder-components.md) for more information, a detailed description of individual bundles expected by the system, and a sample bundling process using Node.js and the Grunt automation library.

## Xperience membership

In Xperience MVC 5 applications, membership authentication and identity features are configured in _Startup.Auth.cs_, located in the _App\_Start_ folder. In ASP.NET Core, you configure these features in the application's startup class.

In MVC 5, you work with identity classes prefixed \*Kentico\*.\*In ASP.NET Core, identity classes adapted by the system are prefixed with _Application\*_: _ApplicationUserStore_, _ApplicationRoleStore_, _ApplicationUserManager_, and _ApplicationUser_. Additionally, there is the default _SignInManager_ type.

The membership integration is registered in **ConfigureServices** in the application's startup. See [Integrating Xperience membership](https://docs.kentico.com/13/managing-users/user-registration-and-authentication/integrating-xperience-membership.md) for more information.

## Preventing memory problems – Cache compacting

Xperience uses [in-memory caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) to optimize the performance of data loading operations. Caching occurs automatically when using most of the default Xperience functionality or API, and you can also leverage the [Caching API](https://docs.kentico.com/13/configuring-xperience/configuring-caching/caching-in-custom-code.md) in your own code when loading data.

However, the system does not limit the size of the cached data, which can potentially consume large amounts of your server's memory and possibly lead to "Out of Memory" exceptions.

If such issues occur in your application, we recommend that you implement a custom solution that monitors memory usage and performs [Cache compacting](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory#memorycachecompact) when it is necessary to free up memory. Compacting attempts to remove a specified percentage of the cache, starting from expired and low priority items.

To compact the Xperience memory cache, call the **CompactCache** method of the **CacheHelper** class. We recommend compacting with a relatively low percentage of removed items, based on your environment and the exact nature of your memory problems.

```csharp title="Example"

using CMS.Helpers;

// Removes 5% of cached items
CacheHelper.CompactCache(.05);

```

The _CompactCache_ method returns a _bool_ value indicating whether the underlying environment supports cache compacting.

## Miscellaneous differences in functionality between the platforms

- Logging to the Windows event log using trace listeners via the [Log to trace and Use EventLog trace listener](https://docs.kentico.com/13/developing-websites/troubleshooting-websites/working-with-the-system-event-log.md) settings (**Settings** -> **System** -> **Event log**) is not supported in ASP.NET Core applications.
- [SharePoint integration](https://docs.kentico.com/13/integrating-3rd-party-systems/integrating-sharepoint.md) is not supported for ASP.NET Core sites.
