I started developing my website in 2012, and it has gone through so many changes. The first version was written in PHP. Then I eventually moved to ASP.NET MVC 3.0. The UI changed a couple of times, and the major version already exceeded number 5.

The last major iteration was switching from classic .NET Framework to .NET Core 2.0 and ASP.NET Core as well.

With those changes, you can get many benefits. Some of them you will find on the official introduction page. You can find many articles regarding ASP.NET Core migration like this. However, they don't cover all cases, that's why I decided to share my own experience.

aspnetcore-migration

Project structure

No significant changes here, Controllers, Views, Models, Areas folders stay in its place. The main difference is Content folder which renamed to wwwroot and represents a home for static content (images, styles, javascript, etc.).

Dependency injection

Another great feature is built-in dependency injection capabilities in ASP.NET Core. You don't need Ninject or Unity or any other DI frameworks. Open your Startup file and place all of your dependencies inside ConfigureServices(IServiceCollection services) method. Use AddTransient, AddSingleton or other methods of interface IServiceCollection to register your services.

Client-side packages

Nuget isn't a client-side package provider for ASP.NET Core applications. If you want to add jQuery to your project, you should consider using different methods.

Visual Studio today supports Bower, NPM and recently LibMan (you can even use other solutions, which is not natively integrated with IDE).

  • Bower: popular client-side package provider, but now it's deprecated. I would not recommend using it.
  • NPM: good choice, but I see two main issues with it:
    • you can't restore your packages to wwwroot folder (which is recommended for static content in ASP.NET Core); to overcome this issue you need to use additional tools like Gulp, Grunt to copy files to the right destination.
    • restored packages will include many other unnecessary files, which will increase the footprint of your application.
  • LibMan: designed as a replacement for Bower, which allows you to easily acquire and manage static, client-side files for your web project from various sources, including CDNJS. It does not have issues like in NPM and sounds like a right decision for me.

Web.config file

Good news, ASP.NET Core isn't tied with IIS anymore. This means that web.config is no longer required (if you decide to host your application on IIS, web.config will be auto-generated). Where should you move all data from config file? Well, consider to spread it across different places:

| Web.config section | New location | ------------- |:-------------| | appSettings | appsettings.json file | | system.web\pages\namespaces | _ViewImports.cshtml file | | system.webServer\httpErrors | app.UseStatusCodePagesWithReExecute("/Error/Index", "?statusCode={0}"), inside Configure method of Startup class | | system.webServer\staticContent | app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = CreateContentTypeProvider() }), inside Configure method of Startup class | | Remaining data | most-likely will go to Startup.cs file |

Global.asax file

This long-living file is also going away with the new framework. Most data will move to Startup.cs file.

Custom model metadata provider

I used custom model metadata provider to override how MVC handles the generation of metadata for my models. If this applies to you, read the text below:

Registration

Before in your Global.asax Application_Start method you would do ModelMetadataProviders.Current = new YourMetadataProvider().

Now in your Startup class you will add services.AddSingleton<IModelMetadataProvider, YourMetadataProvider>() inside ConfigureServices method.

Implementation

Before you would use DataAnnotationsModelMetadataProvider as a base class for your metadata provider, and you would override CreateMetadata method.

Now you will use DefaultModelMetadataProvider as a base class for your metadata provider, and you will override CreateModelMetadata method.

Client-side validation

If you develop HtmlHelper extensions, you might face a situation when you need to get client-side validation attributes.

With classic ASP.NET you can easily do this with GetUnobtrusiveValidationAttributes method:

tagBuilder.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(expression));

A lot of things have changed. Now to accomplish the same goal please use the following code:

var modelExplorer = ExpressionMetadataProvider.FromStringExpression(expression, htmlHelper.ViewContext.ViewData, htmlHelper.MetadataProvider);
var validator = htmlHelper.ViewContext.HttpContext.RequestServices.GetService<ValidationHtmlAttributeProvider>();
validator?.AddAndTrackValidationAttributes(htmlHelper.ViewContext, modelExplorer, expression, tagBuilder.Attributes);

Routing

Only registration has changed in routing.

Before you would do RouteConfig.RegisterRoutes(RouteTable.Routes) inside Global.asax Application_Start method.

Now you will add app.UseMvc(RegisterRoutes) inside Configure method of Startup class.

Areas

To register your application areas:

Before you would do AreaRegistration.RegisterAllAreas() inside Global.asax Application_Start method.

Now you will add routes.MapAreaRoute("your-route-name", "your-area-name", "your-area-template") to your routing configuration for each area.

Small rocks

Here I would like to share some small changes which I noticed during migration.

| Before | After | | ------------- |:-------------| | ActionResult | renamed to IActionResult | | UrlHelper | renamed to IUrlHelper | | MvcHtmlString | renamed to IHtmlContent| | HttpPostedFile | renamed to IFormFile| | Controller.HttpNotFound() | renamed to Controller.NotFound()| | HttpRequestBase | renamed to HttpRequest| | ActionDescriptor.ActionName | renamed to ActionDescriptor.DisplayName| | HttpPostedFile.InputStream | changed to IFormFile.OpenReadStream()| | Request[] | changed to Request.Form[]| | Request.Files.Get() | changed to Request.Form.Files[]| | Request.HttpMethod | renamed to Request.Method| | Request.Url | changed to Request.GetDisplayUrl() (extension method) | | Request.UserHostAddress | changed to Request.HttpContext.Connection.RemoteIpAddress| | Request.UserLanguages | changed to Request.Headers[HeaderNames.AcceptLanguage]| | Request.UrlReferrer | changed to Request.Headers[HeaderNames.Referer]| | Response.Cookies.Set() | changed to Response.Cookies.Append()| | FormsAuthentication.SetAuthCookie() | changed to HttpContext.SignInAsync()| | FormsAuthentication.SignOut() | changed to HttpContext.SignOutAsync()| | AjaxOnlyAttribute | removed, consider your own implementation of this attribute| | HttpException | removed, consider to change your code to something else| | OutputCacheAttribute | removed, consider to change your code to something else| | ChildActionOnlyAttribute | removed, consider switching to View Components|

Summary

I spent roughly three weeks to migrate the whole project to .NET Core and ASP.NET Core. Sometimes not everything was obvious. After spending this time, I can say that the source code became much cleaner, unit-testable, and maintainable. The overall performance of the application has increased as well. Yes, the juice was worth the squeeze.

Hopefully you will find something helpful for your migration story. Goodluck!