Por que eu devo ler este artigo:Este passo a passo foi escrito pelo time da nopCommerce que trabalha com projeto de código aberto e pode ser aplicado a qualquer projeto ASP.NET MVC. Descreve por que você precisa migrar, e por que projetos que não acompanham essas tendências deveriam considerá-las.

Antes de seguir para o passo a passo de como ir para do ASP.NET MVC para ASP.NET Core (usando o nopCommerce como exemplo), apresenta-se uma rápida visão das vantagens desse framework.

Este é um guia prático para migrar um projeto ASP.NET MVC framework para ASP.NET Core. Este passo a passo foi escrito pelo time da nopCommerce que trabalha com projeto de código aberto e pode ser aplicado a qualquer projeto ASP.NET MVC. Descreve por que você precisa migrar, e por que projetos que não acompanham essas tendências deveriam considerá-las.

Antes de seguir para o passo a passo de como ir para do ASP.NET MVC para ASP.NET Core (usando o nopCommerce como exemplo), apresenta-se uma rápida visão das vantagens desse framework.

ASP.NET Core tornou-se muito conhecido e a estrutura desenvolvida possui várias atualizações e melhorias, tornando-a bastante estável, tecnologicamente avançada e resistente a ataques XSRF/CSRF, como mostra a Figura 1.

Estrutura do ASP.NET Core
Figura 1. Estrutura do ASP.NET Core

Cross-platform é uma das características que a distingui, fazendo-o mais e mais popular. De agora em diante sua aplicação web pode rodar em Windows e Unix.

Sua arquitetura é modular, pois o ASP.NET Core vem totalmente na forma de pacotes NuGet. Isto permite a optimização da aplicação, incluindo os pacotes necessários juntamente a aplicação, melhorando a performance da solução e reduzindo o tempo que leva para fazer o upgrade das partes separadas. Esta é a segunda característica importante, que permite ao desenvolvedor integrar novos recursos em suas soluções de maneira mais flexível.

O desempenho é um outro passo para a criação de uma aplicação de alta performance. ASP.NET Core processa 2.300% mais requisições por segundo do que o ASP.NET 4.6, e 800% mais requisições por segundo do que o Node.js, como vemos na Figura 2. Você pode checar esses detalhes de performance e testar você mesmo aqui ou ainda baixando o fonte através do link aqui no post.

Comparação de
desempenho
Figura 2. Comparação de desempenho

Middleware é um novo pipeline leve e rápido para solicitações na aplicação, onde cada parte processa uma solicitação HTTP e decide retornar o resultado ou passa para a próxima parte do middleware. Esta abordagem possibilita ao desenvolvedor controle total sobre o pipeline HTTP e contribui para o desenvolvimento de módulos simples para a aplicação, o que é importante para um projeto de código aberto crescente.

O ASP.NET Core MVC também fornece características que simplificam o desenvolvimento web. O nopCommerce já utiliza algumas delas, tais como templates Modelo-Exibições-Controles (MVC), sintaxe Razor, modelo de dados e validações.

Entre as novas características estão:

  • Auxiliares de Marca: código do lado do servidor para participar da criação e renderização HTML com os elementos em arquivos Razor.
  • View Components: uma nova ferramenta similar com as visões parciais, mas com o desempenho muito superior. nopCommerce utiliza view components ao reutilizar a lógica de renderização e as tarefas que são muito complexas para visões parciais.
  • DI em visões: embora a maioria dos dados exibidos nas visualizações venham dos controladores, nopCommerce também tem visões onde a dependência de injeção é mais conveniente.

É claro que o ASP.NET Core possui muito mais características, mas destacamos algumas das mais interessantes.

Agora considere alguns pontos para lembrar quando transportar sua aplicação para uma nova estrutura.

Migration

As seguintes descrições contêm uma grande quantidade de links para a documentação oficial do ASP.NET Core, que oferecem informações mais detalhadas sobre os tópicos e guia os desenvolvedores que enfrentam estas tarefas pela primeira vez.

Passo 1. Preparando a ferramenta

A primeira coisa que você precisa é migrar o Visual Studio 2017 para versão 15.3 ou posterior e instalar a última versão do .NET Core SDK.

Antes de transferir a aplicação, indica-se usar o .NET Portability Analyzer. Isto pode ser um bom ponto de partida para entender como a transferência de mão de obra intensiva de uma plataforma para outra pode ser. Mesmo assim, esta ferramenta não cobre todos os problemas, pois este processo tem muitas armadilhas a serem resolvidas à medida que surgem. Somente os passos principais e as soluções usadas no projeto nopCommerce são descritas aqui.

A primeira coisa e mais fácil a fazer é atualizar os links para as bibliotecas usadas no projeto para que ele suporte .NET Standard.

Passo 2. Análise de compatibilidade do pacote NuGet para suportar o padrão .NET

Se você usa pacotes NuGet em seus projetos, verifique se eles são compatíveis com o .NET Core. Uma maneira de se fazer isso é usar a ferramenta NuGetPackageExplorer.

Passo 3. O novo formato do arquivo csproj em .NET Core

Uma nova abordagem para adicionar referências a pacotes de terceiros foi introduzida no .NET Core. Ao adicionar uma nova biblioteca de classes, precisamos abrir o arquivo principal do projeto e substituir seu conteúdo da seguinte maneira apresentada na Listagem 1.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
      <TargetFramework>netcoreapp2.2</TargetFramework>   
    </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.6" />
      ...
    </ItemGroup>
    ...
  </Project>
Listagem 1. Conteúdo do arquivo principal

As referências às bibliotecas conectadas serão carregadas automaticamente. Para mais informações de como comparar as propriedades do projeto project.json e CSPROJ, leia a documentação oficial aqui e aqui.

Passo 4. Alteração da Namespace

Delete todos os uses de System.Web e troque por Microsoft.AspNetCore.

Passo 5. Configure o arquivo Startup.cs em vez de usar global.asax

O ASP.NET Core tem uma nova maneira de carregar o aplicativo: o ponto de entrada do aplicativo é o Startup e não existe dependência do arquivo Global.asax, pois a inicialização registra os middlewares no aplicativo. No Startup deve-se incluir o método Configure e o middleware necessário deve ser adicionado ao pipeline no Configure.

Problemas a serem resolvidos no Startup.cs:

  • Configurar o middleware para solicitações MVC e WebAPI;
  • Configurando para:
    • Exception handling: você inevitavelmente enfrentará várias colisões durante a transferência, portanto, esteja pronto e configure o tratamento de exceções no ambiente de desenvolvimento. Com UseDeveloperExceptionPage no NopCommerce inseriu-se middleware para capturar exceções.
    • Roteamento MVC: o registro de novas rotas também foi alterado e o IRouteBuilder agora é usado ao invés de RouteCollection como uma nova maneira de registrar restrições (IActionConstraint).
    • Filtros MVC/WebAPI: os filtros devem ser alterados de acordo com a nova implementação do ASP.NET Core.
    • MVC/WebAPI Formattadores: apresentado na Listagem 2.
    • Model binding : apresentado na Listagem 3.
      //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()));
      Listagem 2. MVC/WebAPI Formattadores
      
      /// <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));
                  
                 
                   .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;
          }
         }
       }
      }
      Listagem 3. Areas
    • Areas: para incluir Area em uma aplicação ASP.NET Core adicione uma rota regular ao arquivo Startup.cs. Desta maneira, ele procurará configurar a área de Admin, como mostra a Listagem 4.
      app.UseMvc(routes => { 
        routes.MapRoute("areaRoute", "{area:exists}/{controller=Admin}/
        {action=Index}/{id?}"); routes.MapRoute( name: "default", template: 
          {controller=Home}/{action=Index}/{id?}"); });
      
      Listagem 4. Configurando Area

      Ao fazer isso, uma pasta com o nome Area com outra pasta dentro denominada Admin deverá estar na raiz do aplicativo, como mostra a Figura 3. Agora, o atributo [Area("Admin")] [Route("admin")] deverá ser usado para conectar o controle com esta área.

Resta apenas criar visualizações para todas as ações descritas no controlador, como vemos na Listagem 5.

Estrutura com a pasta Admin
Figura 3. Estrutura com a pasta Admin
 [Area("Admin")]
 [Route("admin")]
 public class AdminController : Controller
 {    
     public IActionResult Index()
     {
         return View();
     }    
 }
Listagem 5. Criando visualizações

Validação

O IformCollection não deverá ser passado para os controladores, pois neste caso, a validação do servidor ASP.NET está desabilitada. O MVC está suprimindo a validação adicional se o IFormCollection não for nulo. Para resolver esse problema, esta propriedade pode ser adicionada para o modelo, impedindo de passar diretamente para o método controlador. Esta regra trabalha somente se o modelo está disponível, caso contrário, não existirão validações.

As propriedades filhas não são mais validadas automaticamente e devem ser especificadas manualmente.

Passo 6. Migrar manipuladores HTTP e HttpModules para Middleware

Manipuladores e módulos HTTP são de fato muito similares ao conceito de Middleware no ASP.NET Core. Contudo, diferentemente dos módulos, a ordem do middleware é baseada em que são inseridos no pipeline de solicitação. A ordem dos módulos é principalmente baseada nos eventos do ciclo de vida do aplicativo. A ordem do middleware para respostas é oposta à ordem de solicitações, enquanto a ordem dos módulos para solicitações e respostas é a mesma.

Sabendo disso, você pode proceder com as seguintes atualizações:

  • Migrar de módulos para Middleware (AuthenticationMiddleware, CultureMiddleware etc.);
  • Manipuladores para Middleware;
  • Uso de novo middleware.

A autenticação no nopCommerce não usa um sistema de autenticação interno. Para esse propósito é usado o AuthenticationMiddleware, desenvolvido de acordo com a nova estrutura do ASP.NET Core, como mostra a Listagem 6.


  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);
    }
 }
Listagem 6. Uso do AuthenticationMiddleware

O ASP.NET fornece muitos middlewares que você pode usar em seu aplicativo, porém o desenvolvedor pode criar o seu próprio middleware e adicioná-lo no pipeline de solicitações HTTP. Para simplificar o processo, adicionou-se uma interface especial no nopCommerce e agora basta criar uma classe que a implemente, como mostra a Listagem 7.


  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; }
  }
Listagem 7. Interface INopStartup

Na Listagem 8 você pode adicionar e configurar seu middleware.


  /// <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
  }
Listagem 8. Configurando middleware

Passo 7. Usando DI incorporado

Injeção de dependência é uma das características chaves ao projetar um aplicativo ASP.NET Core. Você pode desenvolver aplicativos fracamente acoplados e mais testáveis, modulares e como resultado mais flexíveis na manuseabilidade: isso foi possível seguindo o princípio da inversão de dependência. Para injetar a dependência usamos contêineres IoC (inversão de controle) e em ASP.NET Core esses contêineres são representados pela interface IServiceProvider. Os serviços são instalados no aplicativo no método Startup.ConfigureServices(), como vemos na Listagem 9.

Qualquer serviço registrado pode ser configurado com três escopos:

  • transient
  • scoped
  • singleton
  
services.AddDbContext<ApplicationDbContext>
(options =>
  options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  services.AddSingleton<Isingleton,MySingleton>();
Listagem 9. Escopo do serviço

Passo 8. Usando projetos WebAPI compatíveis com shells (Shim)

Para simplificar a migração de um WebAPI existente, faça o uso do pacote NuGet (Listagem 10) Microsoft.AspNetCore.Mvc.WebApiCompatShim, pois ele suporta os seguintes recursos compatíveis:

  • Adiciona tipos ApiController;
  • Ativa WebAPI estilo model binding;
  • Estende o model binding para que as ações do controlador possam aceitar parâmetros do tipo HttpRequestMessage;
  • Adiciona formatadores de mensagens que permitem ações para retornar resultados do tipo HttpResponseMessage.

  services.AddMvc().AddWebApiConventions();
   
  routes.MapWebApiRoute(name: "DefaultApi",
        template: "api/{controller}/{id?}"
  ); 
Listagem 10. Uso do pacote NuGet

Passo 9. Conversão das Configurações da Aplicação

Algumas configurações foram salvas anteriormente no arquivo web.config. Agora existe uma nova abordagem, baseada nos pares de chave-valores definidos pelos provedores de configuração. Este é o método recomendado pelo ASP.NET Core, e o NopCommerce usa o arquivo appsettings.json.

Você também pode usar o pacote NuGet System.Configuration.ConfigurationManager, mas se por alguma razão quiser continuar usando o *.config a aplicação não pode rodar na plataforma Unix, mas somente no IIS.

Se você quer usar o provedor de configurações de armazenamento de chaves do Azure, então precisará referenciar a migração de conteúdo para o Azure de chaves-valores, mas o projeto não contém essa tarefa.

Passo 10. Conversão conteúdo estático para wwwroot

Para servir conteúdo estático, especifique para o web host a raiz (root) como sendo o diretório corrente, conforme Figura 4. O default é wwwroot. Você pode configurar sua pasta para armazenamento de arquivos estáticos através da configuração do middleware.

Pasta raiz
Figura 4. Pasta raiz

Passo 11. Conversão do Entity Framework para EF Core

Se o projeto usa algumas características do Entity Framework 6 que não são suportadas no EF Core, faz sentido rodar a aplicação no .NET Framework. Nesse caso, rejeitaremos as características multiplataforma e o aplicativo será executado apenas no IIS.

A seguir estão as principais alterações a serem consideradas e apresentadas na Listagem 11:

  • Trocar o namespace System.Data.Entity por Microsoft.EntityFrameworkCore;
  • A assinatura do construtor DbContext foi alterada: agora deve-se injetar DbContextOptions;
  • O método HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) é trocado por ValueGeneratedNever();
  • O método WillCascadeOnDelete(false) foi trocado por OnDelete (DeleteBehavior.Restrict);
  • O método OnModelCreating(DbModelBuilder modelBuilder) é trocado por OnModelCreating(ModelBuilder modelBuilder);
  • O método HasOptional não está mais disponível;
  • A configuração do objeto foi alterada e agora o OnModelCreating está sendo usado, pois o EntityTypeConfiguration não está mais disponível;
  • Já o atributo ComplexType não está mais disponível;
  • A interface IDbSet foi trocada por DbSet;
  • O suporte a tipos complexos ComplexType apareceu no EF Core 2 com o tipo Owned Entity, e tabelas sem chave primária com o QueryType no EF Core 2.1;
  • As Chaves externas no EF Core geram Shadow Properties usando a template [Entity]Id, diferentemente do EF6, que usa a template [Entity]_Id. Portanto, adicione chaves externas como uma propriedade regular a entidade.
  • Para suporte a DI no DbContext, configure seu DbContex em ConfigureServices.

  /// <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);
  }
Listagem 11. Conversão do Entity Framework para EF Core

Para verificar se o EF Core gera uma estrutura de banco de dados semelhante ao Entity Framework quando migrar, utilize a ferramenta SQL Compare.

Passo 12. Removendo todas as referências HttpContext, substituindo classes obsoletas e alterando o namespace

Durante o projeto de migração você encontrará muitas classes que deverão ser renomeadas ou removidas e agora é necessário cumprir com os novos requisitos. Por isso, aqui está uma lista das principais alterações:

  • HttpPostedFileBase 🡪 FormFile
  • Acesso HttpContext pode ser acessado via IHttpContextAccessor
  • HtmlHelper 🡪 HtmlHelper
  • ActionResult 🡪 ActionResult
  • HttpUtility 🡪 WebUtility
  • ISession em vez de HttpSessionStateBase, acessível em HttpContext.Session de Microsoft.AspNetCore.Http
  • Request.Cookies retorna IRequestCookieCollection: IEnumerable <KeyValuePair<string, string> >;
  • Em vez de HttpCookie usamos KeyValuePair <string, string> de Microsoft.AspNetCore.Http.

Trocas no namespace:

  • 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

Outros:

  • MvcHtmlString.IsNullOrEmpty(IHtmlString) 🡪 String.IsNullOrEmpty(variable.ToHtmlString());
  • [ValidateInput (false)] – não existe mais e não é mais necessário;
  • HttpUnauthorizedResult 🡪 UnauthorizedResult;
  • [AllowHtml] – diretiva não existe mais e não é necessária;
  • TagBuilder.SetInnerText é um método trocado por InnerHtml.AppendHtml;
  • JsonRequestBehavior.AllowGet quando retorna JSON não é mais necessário;
  • HttpUtility.JavaScriptStringEncode. JavaScriptEncoder.Default.Encode Request.RawUrl. Request.Path + Request.QueryString deve ser conectado separadamente;
  • AllowHtmlAttribute é uma classe que não existe mais;
  • XmlDownloadResult pode usar apenas return File(Encoding.UTF8.GetBytes (xml), "application / xml", "filename.xml");
  • A diretiva [ValidateInput(false)] não existe mais e não é necessário.

Passo 13. Atualização de autenticação e autorização

Como já foi mencionado acima, o projeto nopCommerce não tem o sistema de autenticação embutido, pois é implementado em uma camada de middleware separado. No entanto, o ASP.NET Core possui seu próprio sistema para fornecer credenciais. Você pode ver a documentação para saber mais detalhes.

Em relação a proteção de dados, o nopCommerce não usa mais MachineKey. Em vez disso, usamos o recurso interno de proteção de dados. Por padrão, as chaves são geradas quando o aplicativo é iniciado. O armazenamento de dados pode ser:

  • Sistema de arquivos – armazenamento de chaves baseado em arquivos;
  • Armazenamento Azure – chaves de proteção de dados no armazenamento de objetos BLOB Azure;
  • Redis – chaves de proteção de dados no cache Redis;
  • Registry – usado se a aplicação não tem acesso ao sistema de arquivos;
  • EF Core – chaves são armazenadas no banco de dados.

Caso os provedores internos não sejam adequados pode-se especificar seu próprio provedor de armazenamento de chaves criando um IXmlRepository.

Passo 14. Atualização JS/CSS

A maneira de usar os recursos estáticos mudou e agora todos eles devem ser armazenados na pasta da raiz do projeto wwwroot, a menos que outras configurações sejam feitas.

Ao usar blocos internos de JavaScript, recomendamos movê-los para o final da página. Apenas use o atributo asp-location = "Footer" para suas tags <script>. As mesmas regras se aplicam aos arquivos .js.

Use a extensão BundlerMinifier como substituto para a System.Web.Optimization, pois isso permitirá o empacotamento e a minificação do JavaScript e CSS durante a criação do projeto (conforme a documentação).

Passo 15. Conversão de exibições

Primeiro, Child Actions não são mais usadas, já que o ASP.NET Core sugere o uso de uma nova ferramenta de alto desempenho. Os ViewComponents são chamados de forma assíncrona.

Veja na Listagem 12 como obter uma string da ViewComponent.


  /// <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();
     }
 }
Listagem 12. Obtendo uma string com a ViewComponent

Perceba que não é mais necessário usar o HtmlHelper, pois o ASP.NET Core inclui muito auxiliares que são as auxiliares de Tag. Quando o aplicativo está em execução, o mecanismo Razor as processa no servidor e converte-as em elementos HTML padrão. Isso torna o desenvolvimento de aplicativos muito mais fácil, mas é claro que você pode implementar seus próprios auxiliares de tags.

Inicializamos o uso de injeção de dependência nas visualizações ao invés de habilitar configurações e serviços usando o EngineContext. Com isso, os principais pontos a serem considerados na conversão são:

  • Converter Views/web.config para Views/_ViewImports.cshtml para importar namespaces e injetar dependência. Este arquivo não suporta outros recursos do Razor, como definições de função e seção.
  • Converta namespaces.add to @using
  • Portar qualquer configuração para a configuração principal do aplicativo.
  • Scripts.Render e Styles.Render não existem, então os substitua por links para dados de saída Libman ou BundlerMinifier.

Conclusão

O processo de migração de um grande aplicativo Web é uma tarefa muito demorada que, como regra, não pode ser realizada sem as “armadilhas”. Podemos planejar migrar para uma nova estrutura, mas quando a sua primeira versão estável ficou pronta não foi possível lançá-la imediatamente pois haviam alguns recursos críticos que não tinham sido transferidos para o .NET Core. Em particular, os relacionados ao Entity Framework.

Portanto, foi necessário fazer o lançamento com uma abordagem mista, com a arquitetura do .NET Core e as dependências do .NET Framework, que por si só formam uma solução exclusiva. Ser o primeiro não é fácil e a certeza de que foi a escolha certa, com o apoio da nossa enorme comunidade.

O projeto foi totalmente adaptado após o lançamento do .NET Core 2.1, tendo nessa época uma solução estável já trabalhando na nova arquitetura. Restou apenas substituir alguns pacotes e reescrever o trabalho com o EF Core. Assim, transcorreram vários meses e duas versões lançadas para migrar completamente para a nova estrutura.

Pode-se dizer com confiança que este é o primeiro grande projeto a realizar essa migração. Neste guia foi mostrado todo o processo de migração de forma estruturada e descrevemos vários gargalos para que outros desenvolvedores possam confiar nesse material e seguir o roteiro ao resolver a mesma tarefa.