How to Migrate a Project From ASP.NET MVC to ASP.NET Core
Tags: nopcommerce, aspnetcore, dotnet, ecommerce
Published: 2019-11-12 15:56:42

## Step-by-step guide

Here is a practical guide on migrating a project from ASP.NET MVC framework to ASP.NET Core. Step-by-step instruction written by the team of [nopCommerce](https://www.nopcommerce.com/) open-source project can be easily applied to any ASP.NET MVC project. It also describes why you might need to upgrade, and why projects that do not yet keep up with pace should consider it.

## Why porting to ASP.NET Core

Before proceeding to the steps of porting from ASP.NET MVC to ASP.NET Core (using nopCommerce as an example), let's take the quick overview of this framework advantages.

ASP.NET Core is already a fairly well-known and developed framework with several major updates making it quite stable, technologically advanced and resistant to XSRF/CSRF attacks.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/3hr206qrfcsuyc6iveet.png)

Cross-platform is one of the distinguishing features making it more and more popular. From now on, your web application can run both in Windows and Unix environment.

Modular architecture - ASP.NET Core comes fully in the form of NuGet packages, it allows optimizing the application, including the selected required packages. This improves solution performance and reduces the time it takes to upgrade separate parts. This is the second important characteristic that allows developers to integrate new features into their solution more flexible.

Performance is another step towards building a high-performance application. ASP.NET Core handles 2.300% more requests per second than ASP.NET 4.6, and 800% more requests per second than node.js. You can check the detailed performance tests yourself [here](https://github.com/aspnet/benchmarks "here") or [here](https://www.techempower.com/benchmarks/#section=test&runid=8ca46892-e46c-4088-9443-05722ad6f7fb&hw=ph&test=plaintext "here").

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/wqpoyno9yhz91hjh14v1.png)

Middleware is a new light and fast modular pipeline for in-app requests. Each part of middleware processes an HTTP request, and then either decides to return the result or passes the next part of middleware. This approach gives the developer full control over the HTTP pipeline and contributes to the development of simple modules for the application, which is important for a growing open-source project.

Also, ASP.NET Core MVC provides features that simplify web development. nopCommerce already used some of them, such as the Model-View-Controller template, Razor syntax, model binding, and validation. Among the new features are:

- Tag Helpers. Server-part code for participation in creating and rendering HTML elements in `Razor` files.

- View components. A new tool, similar to partial views, but of much higher performance. nopCommerce uses view components when reusing rendering logic is required and the task is too complex for partial view.

- DI in views. Although most of the data displayed in views comes from the controller, nopCommerce also has views where dependency injection is more convenient.

Of course, ASP.NET Core has much more features, we viewed the most interesting ones only.
Now let's consider the points to keep in mind when porting your app to a new framework.

## Migration

The following descriptions contains large amount of links to the official ASP.NET Core documentation to give more detailed information about the topic and guide developers who face such a task the first time.

### Step 1. Preparing a toolkit

The first thing you need is to upgrade Visual Studio 2017 to version 15.3 or later and install the latest version of .NET Core SDK.

Before porting, we advise to use [.Net Portability Analyzer](https://docs.microsoft.com/dotnet/standard/analyzers/portability-analyzer ".Net Portability Analyzer"). This can be a good starting point to understand how labor-intensive porting from one platform to another will be. Nevertheless, this tool does not cover all the issues, this process has many pitfalls to be solved as they emerge. Below we will describe the main steps and the solutions used in nopCommerce project.

And the first and the easiest thing to do is to update links to the libraries used in the project so to they support .NET Standard.

### Step 2. NuGet package compatibility analysis to support .Net standard

If you use NuGet packages in your project, check whether they are compatible with .NET Core. One way to do this is to use the [NuGetPackageExplorer](https://github.com/NuGetPackageExplorer/NuGetPackageExplorer "NuGetPackageExplorer") tool.

### Step 3. The new format of csproj file in .NET Core

A new approach for adding references to third-party packages was introduced in .NET Core. When adding a new class library, you need to open the main project file and replace its contents as follows:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.6" />
    ...
  </ItemGroup>
  ...
</Project>
```

References to the connected libraries will be loaded automatically. For more information on comparing the `project.json` and `CSPROJ` properties, read the official documentation [here](https://docs.microsoft.com/dotnet/core/tools/project-json-to-csproj "here") and [here](https://docs.microsoft.com/dotnet/core/tools/csproj "here").

### Step 4. Namespace update

Delete all uses of `System.Web` and replace them with `Microsoft.AspNetCore`.

### Step 5. Configure the `Startup.cs` file instead of using `global.asax`

ASP.NET Core has a new way of loading the app. The app entry point is `Startup`, and there is no dependency on the *Global.asax* file. `Startup` registers the middleware in the app. `Startup` must include the `Configure` method. The required middleware should be added to the pipeline in `Configure`.

Issues to solve in `Startup.cs`:

- Configuring middleware for MVC and WebAPI requests
- Configuring for:
  - [Exception handling](https://docs.microsoft.com/aspnet/core/fundamentals/error-handling?view=aspnetcore-2.2 "Exception handling")
You will inevitably face various collisions during porting, thus be ready and set up exception handling in the development [environment](https://docs.microsoft.com/aspnet/core/fundamentals/environments?view=aspnetcore-2.2 "environment"). With [UseDeveloperExceptionPage](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.builder.developerexceptionpageextensions.usedeveloperexceptionpage?view=aspnetcore-3.0 "UseDeveloperExceptionPage"), we add middleware to catch exceptions.
  - [MVC routing](https://docs.microsoft.com/aspnet/core/fundamentals/routing?view=aspnetcore-2.2 "MVC routing").
[Registration of new routes](https://docs.microsoft.com/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2) has also been changed. IRouteBuilder is now used instead of RouteCollection, as a new way to [register restrictions](https://docs.microsoft.com/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2#understanding-iactionconstraint)  (IActionConstraint)
  - [MVC/WebAPI filters](https://docs.microsoft.com/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2). The filters should be updated in accordance with the [new implementation of ASP.NET Core](https://docs.microsoft.com/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.0).
  - [MVC/WebAPI Formatters](https://docs.microsoft.com/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-2.2)
  - [Binding models](https://docs.microsoft.com/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.2)

```cs
//add basic MVC feature
var mvcBuilder = services.AddMvc();

//add custom model binder provider (to the top of the provider list)
mvcBuilder.AddMvcOptions(options => options.ModelBinderProviders.Insert(0, new  NopModelBinderProvider()));
```

```cs
/// <summary>
/// Represents model binder provider for the creating NopModelBinder
/// </summary>
public class NopModelBinderProvider : IModelBinderProvider
{
    /// <summary>
    /// Creates a nop model binder based on passed context
    /// </summary>
    /// <param name="context">Model binder provider context</param>
    /// <returns>Model binder</returns>
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));


        var modelType = context.Metadata.ModelType;
        if (!typeof(BaseNopModel).IsAssignableFrom(modelType))
            return null;

        //use NopModelBinder as a ComplexTypeModelBinder for BaseNopModel
        if (context.Metadata.IsComplexType &&!context.Metadata.IsCollectionType)
        {
            //create binders for all model properties
            var propertyBinders = context.Metadata.Properties
                .ToDictionary(modelProperty => modelProperty, modelProperty =>  context.CreateBinder(modelProperty));

            return new NopModelBinder(propertyBinders,  EngineContext.Current.Resolve<ILoggerFactory>());
        }

        //or return null to further search for a suitable binder
        return null;
    }
}
```

- [Areas](https://docs.microsoft.com/aspnet/core/mvc/controllers/areas?view=aspnetcore-2.2 "Areas") To include Area in an ASP.NET Core app, add a regular route to the `Startup.cs` file. For example, this way it will look for configuring *Admin area*:

```cs
app.UseMvc(routes => { routes.MapRoute("areaRoute", "{area:exists}/{controller=Admin}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
```

When doing it, the folder with the name *Area* with the *Admin folder* inside, should be in the app root. Now the attribute `[Area("Admin")] [Route("admin")]`. will be used to connect the controller with this area.
It remains only to create views for all the actions described in the controller.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/tvt4wro4ldf2d37knugv.png)

```cs
[Area("Admin")]
[Route("admin")]
public class AdminController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}
```

#### Validation

*IFormCollection* should not be passed to the controllers since in this case, asp.net server validation is disabled. To solve the problem, this property might be added to the model, this will prevent us from passing directly to the controller method. This rule works only if a model is available, if there is no model, there will be no validation.

Child properties are no longer automatically validated and should be specified manually.

### Step 6. Migrate HTTP handlers and HttpModules to Middleware

HTTP handlers and HTTP modules are in fact very similar to the concept of [Middleware in ASP.NET Core,](https://docs.microsoft.com/aspnet/core/migration/http-modules?view=aspnetcore-2.2 "Middleware in ASP.NET Core,") but unlike modules, the middleware order is based on the order in which they are inserted into the request pipeline. The order of modules is mainly based on the events of [application life cycle](https://docs.microsoft.com/previous-versions/ms227673(v=vs.140)?redirectedfrom=MSDN "application life cycle"). The order of middleware for responses is opposite to the order for requests, while the order of modules for requests and responses is the same. Knowing this, you can proceed with the update.

What should be updated:

- Migration of modules for *Middleware* (*AuthenticationMiddleware*, *CultureMiddleware*, etc.)
- [Handlers](https://docs.microsoft.com/aspnet/core/migration/http-modules?view=aspnetcore-2.2#migrating-handler-code-to-middleware) to *Middleware*
- Use of new *middleware*

Authentication in nopCommerce does not use a built-in authentication system; for this purpose, AuthenticationMiddleware developed in accordance with the new ASP.NET Core structure is used.

```cs
public class AuthenticationMiddleware
{
   private readonly RequestDelegate _next;
   public AuthenticationMiddleware(IAuthenticationSchemeProvider schemes, RequestDelegate next)
   {
       Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
       _next = next ?? throw new ArgumentNullException(nameof(next));
   }

   public IAuthenticationSchemeProvider Schemes { get; set; }

   public async Task Invoke(HttpContext context)
   {
       context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
       {
           OriginalPath = context.Request.Path,
           OriginalPathBase = context.Request.PathBase
       });

       var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
       foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
       {
           try
           {
               if (await handlers.GetHandlerAsync(context, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync())
                   return;
           }
           catch
           {
               // ignored
           }
       }

       var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
       if (defaultAuthenticate != null)
       {
           var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
           if (result?.Principal != null)
           {
               context.User = result.Principal;
           }
       }
       await _next(context);
   }
}
```

ASP.NET provides a lot of built-in middleware that you can use in your application, but the developer can also [create his own middleware](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/write?view=aspnetcore-2.2 "create his own middleware") and add it to HTTP request pipeline. To simplify this process, we added a special interface to nopCommerce, and now it’s enough to just create a class that implements it.

```cs
public interface INopStartup
{
    /// <summary>
    /// Add and configure any of the middleware
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="configuration">Configuration of the application</param>
    void ConfigureServices(IServiceCollection services, IConfiguration configuration);

    /// <summary>
    /// Configure the using of added middleware
    /// </summary>
    /// <param name="application">Builder for configuring an application's request pipeline</param>
    void Configure(IApplicationBuilder application);

    /// <summary>
    /// Gets order of this startup configuration implementation
    /// </summary>
    int Order { get; }
}
```

Here you can add and configure your middleware:

```cs
/// <summary>
/// Represents object for the configuring authentication middleware on application startup
/// </summary>
public class AuthenticationStartup : INopStartup
{
    /// <summary>
    /// Add and configure any of the middleware
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="configuration">Configuration of the application</param>
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        //add data protection
        services.AddNopDataProtection();

        //add authentication
        services.AddNopAuthentication();
    }

    /// <summary>
    /// Configure the using of added middleware
    /// </summary>
    /// <param name="application">Builder for configuring an application's request pipeline</param>
    public void Configure(IApplicationBuilder application)
    {
        //configure authentication
        application.UseNopAuthentication();
    }

    /// <summary>
    /// Gets order of this startup configuration implementation
    /// </summary>
    public int Order => 500; //authentication should be loaded before MVC
}
```

### Step 7. Using built-in DI

[Dependency](https://docs.microsoft.com/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2 "Dependency") injection is one of the key features when designing an app in ASP.NET Core. You can develop loosely coupled applications that are more testable, modular and as a result more maintainable. This was made possible by following the principle of dependency inversion. To inject dependencies, we used IoC (*Inversion of Control*) containers. In ASP.NET Core, such a container is represented by the *IServiceProvider* interface. Services are installed in the app in the `Startup.ConfigureServices()` method.

Any registered service can be configured with three scopes:

- transient
- scoped
- singleton

```cs
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<Isingleton,MySingleton>();
```

### Step 8. Using WebAPI project compatibility shells (Shim)

To simplify migration of an existing Web API, we advise using the NuGet package  [Microsoft.AspNetCore.Mvc.WebApiCompatShim](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.WebApiCompatShim "Microsoft.AspNetCore.Mvc.WebApiCompatShim"). It supports the following [compatible features](https://docs.microsoft.com/aspnet/core/migration/webapi?view=aspnetcore-2.2#compatibility-shim "compatible features"):

- Adding *ApiController* type;
- Enabling web API style model binding;
- Extending model binding so that controller actions can accept `HttpRequestMessage` type parameters;
- Adding message formatters enabling actions to return results of `HttpResponseMessage type`.

```cs
services.AddMvc().AddWebApiConventions();

routes.MapWebApiRoute(name: "DefaultApi",
    template: "api/{controller}/{id?}"
);
```

### Step 9. Porting Application Configuration

Some settings were previously saved in the web.config file. Now we use a [new approach](https://docs.microsoft.com/aspnet/core/fundamentals/configuration/index?tabs=basicconfiguration&view=aspnetcore-2.2 "new approach") based on the key-value pairs set by *configuration providers*. This is the recommended method in ASP.NET Core, and we use the `appsettings.json` file.

You can also use the NuGet package 'System.Configuration.ConfigurationManager' if for some reason you want to continue using `*.config`. In this case, the app cannot run on Unix platforms, but on IIS only.

If you want to use the Azure key storage configuration provider, then you need to refer to [Migrating content to Azure Key Vault](https://docs.microsoft.com/aspnet/core/security/key-vault-configuration?view=aspnetcore-2.2 "Migrating content to Azure Key Vault"). Our project did not contain such a task.

### Step 10. Porting static content to wwwroot

To serve [static content](https://docs.microsoft.com/aspnet/core/fundamentals/static-files?view=aspnetcore-2.2 "static content"), specify to web host the content root of the current directory. The default is wwwroot. You can configure your folder for storing static files by setting up middleware.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/5acm12vq68gv5okha66j.png)

### Step 11. Porting EntityFramework to EF Core

If the project uses some specific [features of Entity Framework 6](https://docs.microsoft.com/ef/efcore-and-ef6/ "features of Entity Framework 6"), that are [not supported](https://docs.microsoft.com/ef/efcore-and-ef6/ "not supported") in *EF Core*, it makes sense to run the application on the *NET Framework*. Though in this case, we will have to reject the multi-platform feature, and the application will run on *Windows* and *IIS* only.

Below are the main changes to be considered:

- `System.Data.Entity` namespace is replaced by `Microsoft.EntityFrameworkCore`;
- The signature of the `DbContext` constructor has been changed. Now you should inject `DbContextOptions`;
- `HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)` method is replaced by `ValueGeneratedNever()`;
- `WillCascadeOnDelete(false)` method is replaced by `OnDelete (DeleteBehavior.Restrict)`;
- `OnModelCreating(DbModelBuilder modelBuilder)` method is replaced by `OnModelCreating(ModelBuilder modelBuilder)`;
- `HasOptional` method is no longer available;
- Object configuration is changed, now `OnModelCreating` is using since `EntityTypeConfiguration` is no longer available;
- `ComplexType attribute` is no longer available;
- `IDbSet` interface is replaced by `DbSet`;
- `ComplexType` - complex type support appeared in EF Core 2 with the [Owned Entity](https://docs.microsoft.com/ef/core/modeling/owned-entities "Owned Entity") type, and tables without Primary Key with [QueryType](https://docs.microsoft.com/ef/core/modeling/keyless-entity-types "QueryType") in EF Core 2.1;
- External keys in EF Core generate [shadow properties](https://docs.microsoft.com/ef/core/modeling/shadow-properties "shadow properties") using the [Entity]Id template, unlike EF6, that uses the `[Entity]_Id` template. Therefore, add external keys as a regular property to the entity first.
- To support `DI` for `DbContext`, configure your `DbContex` in `ConfigureServices`.

```cs
/// <summary>
/// Register base object context
/// </summary>
/// <param name="services">Collection of service descriptors</param>
public static void AddNopObjectContext(this IServiceCollection services)
{
    services.AddDbContextPool<NopObjectContext>(optionsBuilder =>
    {
        optionsBuilder.UseSqlServerWithLazyLoading(services);
    });
}

/// <summary>
/// SQL Server specific extension method for Microsoft.EntityFrameworkCore.DbContextOptionsBuilder
/// </summary>
/// <param name="optionsBuilder">Database context options builder</param>
/// <param name="services">Collection of service descriptors</param>
public static void UseSqlServerWithLazyLoading(this DbContextOptionsBuilder optionsBuilder, IServiceCollection services)
{
    var nopConfig = services.BuildServiceProvider().GetRequiredService<NopConfig>();

    var dataSettings = DataSettingsManager.LoadSettings();
    if (!dataSettings?.IsValid ?? true)
        return;

    var dbContextOptionsBuilder = optionsBuilder.UseLazyLoadingProxies();

    if (nopConfig.UseRowNumberForPaging)
        dbContextOptionsBuilder.UseSqlServer(dataSettings.DataConnectionString, option => option.UseRowNumberForPaging());
    else
        dbContextOptionsBuilder.UseSqlServer(dataSettings.DataConnectionString);
}
```

To verify that *EF Core* generates a similar database structure as Entity Framework when [migrating](https://docs.microsoft.com/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli "migrating"), use the  [SQL Compare](https://www.red-gate.com/products/sql-development/sql-compare/ "SQL Compare") tool.

### Step 12. Removing all HttpContext references and replacing obsolete classes and changing the namespace

During the project migration, you will find that a sufficiently large number of classes have been renamed or moved, and now you should comply with the new requirements. Here is a list of the main changes you may encounter:

- `HttpPostedFileBase` 🡪 `FormFile`
- Access `HttpContext` can now be accessed via `IHttpContextAccessor`
- `HtmlHelper` 🡪 `HtmlHelper`
- `ActionResult` 🡪 `ActionResult`
- `HttpUtility` 🡪 `WebUtility`
- ISession instead of `HttpSessionStateBase` accessible from `HttpContext.Session.` from `Microsoft.AspNetCore.Http`
- `Request.Cookies` returns `IRequestCookieCollection`: `IEnumerable <KeyValuePair<string, string> >`, then instead of `HttpCookie` we use `KeyValuePair <string, string>` from `Microsoft.AspNetCore.Http`

Namespace replacement:

- `SelectList` 🡪 `Microsoft.AspNetCore.Mvc.Rendering`
- `UrlHelper` 🡪 `WebUtitlity`
- `MimeMapping` 🡪 `FileExtensionContentTypeProvider`
- `MvcHtmlString` 🡪 `IHtmlString` and `HtmlString`
- `ModelState`, `ModelStateDictionary`, `ModelError` 🡪 `Microsoft.AspNetCore.Mvc.ModelBinding`
- `FormCollection` 🡪 `IFormCollection`
- `Request.Url.Scheme` 🡪 `this.Url.ActionContext.HttpContext.Request.Scheme`

Other:

- `MvcHtmlString.IsNullOrEmpty(IHtmlString)` 🡪 `String.IsNullOrEmpty(variable.ToHtmlString())`
- `[ValidateInput (false)]` - does not exist anymore and is no longer needed
- `HttpUnauthorizedResult` 🡪 `UnauthorizedResult`
- `[AllowHtml]` - directive does not exist anymore and is no longer needed
- `TagBuilder.SetInnerText` method is replaced by `InnerHtml.AppendHtml`
- `JsonRequestBehavior.AllowGet` when returning Json is no longer needed
- `HttpUtility.JavaScriptStringEncode.`🡪 `JavaScriptEncoder.Default.Encode`
- `Request.RawUrl.` `Request.Path + Request.QueryString` should be separately connected
- `AllowHtmlAttribute` - class no longer exists
- `XmlDownloadResult` - now you can use just `return File(Encoding.UTF8.GetBytes (xml), "application / xml", "filename.xml")`;
- `[ValidateInput(false)]` - directive does not exist anymore and is no longer needed

### Step 13. Authentication and authorization update

As was already mentioned above, nopCommerce project does not involve the built-in authentication system, it is implemented in a separate middleware layer. However, ASP.NET Core has its own system for credentials providing. You can view the [documentation](https://docs.microsoft.com/aspnet/core/security/?view=aspnetcore-2.2#authentication-vs-authorization "documentation") to know about them in details.

As for data protection, we no longer use  [MachineKey](https://docs.microsoft.com/aspnet/core/security/data-protection/compatibility/replacing-machinekey?view=aspnetcore-2.2 "MachineKey"). Instead, we use the built-in data protection feature. By default, keys are generated when the application starts. As the data storage can be:

- `File system` - file system-based keystore
- `Azure Storage` - data protection keys in *Azure BLOB* object storage
- `Redis` - data protection keys in the Redis cache
- `Registry` - used if the application does not have access to the file system
- `EF Core` - keys are stored in the database

If the built-in providers are not suitable, you can specify your own key storage provider by making a custom [IXmlRepository](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.dataprotection.repositories.ixmlrepository?view=aspnetcore-3.0 "IXmlRepository").

### Step 14. JS/CSS update

The way of using static resources has changed, now they should all be stored in the root folder of the project `wwwroot`, unless other settings are made.

When using javascript built-in blocks, we recommend moving them to the end of the page. Just use the `asp-location = "Footer"` attribute for your `script` tags. The same rule applies to js files.

Use the [BundlerMinifier](https://github.com/madskristensen/BundlerMinifier "BundlerMinifier") extension as a replacement for `System.Web.Optimization` - this will enable bundling and minification. JavaScript and CSS while building the project ([view the documentation](https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification?view=aspnetcore-3.0&tabs=visual-studio "view the documentation")).

### Step 15. Porting views

First of all, `Child Actions` are no longer used, instead, ASP.NET Core suggests using a new high-performance tool - [ViewComponents](https://docs.microsoft.com/aspnet/core/mvc/views/view-components?view=aspnetcore-2.2 "ViewComponents") called asynchronously.

How to get a string from *ViewComponent*:

```cs
/// <summary>
/// Render component to string
/// </summary>
/// <param name="componentName">Component name</param>
/// <param name="arguments">Arguments</param>
/// <returns>Result</returns>
protected virtual string RenderViewComponentToString(string componentName, object arguments = null)
{
    if (string.IsNullOrEmpty(componentName))
        throw new ArgumentNullException(nameof(componentName));

    var actionContextAccessor = HttpContext.RequestServices.GetService(typeof(IActionContextAccessor)) as IActionContextAccessor;
    if (actionContextAccessor == null)
        throw new Exception("IActionContextAccessor cannot be resolved");

    var context = actionContextAccessor.ActionContext;

    var viewComponentResult = ViewComponent(componentName, arguments);

    var viewData = ViewData;
    if (viewData == null)
    {
        throw new NotImplementedException();
    }

    var tempData = TempData;
    if (tempData == null)
    {
        throw new NotImplementedException();
    }

    using (var writer = new StringWriter())
    {
        var viewContext = new ViewContext(
            context,
            NullView.Instance,
            viewData,
            tempData,
            writer,
            new HtmlHelperOptions());

        // IViewComponentHelper is stateful, we want to make sure to retrieve it every time we need it.
        var viewComponentHelper = context.HttpContext.RequestServices.GetRequiredService<IViewComponentHelper>();
        (viewComponentHelper as IViewContextAware)?.Contextualize(viewContext);

        var result = viewComponentResult.ViewComponentType == null ?
            viewComponentHelper.InvokeAsync(viewComponentResult.ViewComponentName, viewComponentResult.Arguments):
            viewComponentHelper.InvokeAsync(viewComponentResult.ViewComponentType, viewComponentResult.Arguments);

        result.Result.WriteTo(writer, HtmlEncoder.Default);
        return writer.ToString();
    }
}
```

Note that there is no need to use `HtmlHelper` anymore, ASP.NET Core includes many auxiliary built-in [Tag Helpers](https://docs.microsoft.com/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.2#built-in-aspnet-core-tag-helpers "Tag Helpers"). When the application is running, the *Razor* engine handles them on the server and ultimately converts to standard `html` elements. This makes application development a whole lot easier. And of course, you can implement your own `tag helpers.`

We started using dependency injection in views instead of enabling settings and services using `EngineContext`.

So, the main points on porting views are as follows:

- Convert `Views/web.config` to `Views/_ViewImports.cshtml` - to import namespaces and inject dependencies. This file does not support other Razor features, such as function and section definitions
- Convert `namespaces.add` to `@using`
- Porting any settings to the main application configuration
- `Scripts.Render` and `Styles.Render` do not exist. Replace with links to output data of `libman` or `BundlerMinifier`

## Conclusion

The process of migrating a large web application is a very time-consuming task which, as a rule, cannot be carried out without the pitfalls. We planned to migrate to a new framework as soon as its first stable version was released but were not able to make it right away, as there were some critical features that had not been transferred to .NET Core, in particular, those related to EntityFramework. Therefore, we had first to make our release using a mixed approach - the .NET Core architecture with the .NET Framework dependencies, which in itself is a unique solution. Being first is not easy, but we are sure we’ve made the right choice, and our huge community supported us in this.

We were able to fully adapt our project after the release of .NET Core 2.1, having by that time a stable solution already working on the new architecture. It remained only to replace some packages and rewrite the work with EF Core. Thus, it took us several months and two released versions to completely migrate to the new framework.

In this guide, we tried to put together the entire migration process in a structured form and describe various bottlenecks so that other developers could rely on this material and follow the roadmap when solving the same task.

To learn more about nopCommerce visit our [GitHub](https://github.com/nopSolutions/nopCommerce)