Fork me on GitHub

ASP.NET 5 RC1 升级 ASP.NET Core 1.0 RC2 记录

升级文档:

ASP.NET Core 1.0 RC2 发布:解读发布:.NET Core RC2 and .NET Core SDK Preview 1

之前,使用 ASP.NET 5 RC1 开发了一个项目,并且这个项目已经用于生产环境,项目中包含的一些东西:

  • “伪 DDD” 框架(https://github.com/yuezhongxin/DDD.Sample)
  • ASP.NET 5 Web 和 ASP.NET 5 WebApi 项目
  • xUnit 单元测试(包含对 WebApi 的测试)
  • EntityFramework 程序包
  • AutoMapper 程序包
  • 自定义开发的程序包
  • Bootstrap 前端框架
  • Log 日志记录
  • Identity 身份验证
  • HttpClient 调用其他 WebApi

现在要升级到 ASP.NET Core 1.0 RC2,官方升级文档只是简单的介绍了下,实际升级的过程中还是遇到了不少的问题,我们平常开发 ASP.NET 应用程序,上面所列出的东西基本都包含了,所以,下面纪录升级的过程,包含一些问题和解决方式,希望可以帮助到大家。

1. 代码更新

升级的首要前提,开发环境需要安装:

然后,我们打开 ASP.NET 5 RC1 应用程序的解决方案:

映入眼帘的是程序包还原失败,并且是一大堆错误:

什么鬼?有点莫名其妙,程序包还原失败的原因是,在原有的程序包源中找不到了,具体就是微软把相关程序包都删掉了,好坑啊😂,我还以为是程序包源变更了,后来我找了当时的好几个程序包源,试了都不行,那是不是所有的程序包都删掉了?其实不是,微软只保留了这种1.0.0-rc1-final版本的程序包,其他的基本上都删掉了,比如下面的这种版本程序包:

这个项目我基本上用的都是1.0.0-rc2-xxxxx版本的程序包,比较坑,为什么用这种版本的?主要是当时解决一些特殊问题,就不详细说了,如果大家感兴趣的话,可以查看下相关文章,如果你的项目用的是1.0.0-rc1-final版本的程序包,那么还原和生成应该是成功的。

当时 ASP.NET Core 1.0 RC2 一发布出来,我开发环境就安装更新了,然后前几天这个项目有一个 bug 需要进行修复,打开之后就是上面这个鸟样,所以,修复 bug 必须要升级到 RC2 版本,这也是我这次升级的主要原因。

上面废话有点多,主要想发泄吐槽下,言归正传,我们在代码更新之前,需要了解下 .NET Platform Standard 的概念,详细可以看我前两天写的一篇文章:理解 .NET Platform Standard,这篇文章就是我在升级的过程中记录的,因为之前对它的不了解,所以后来踩了一些坑,简单来说,就是你想让你的应用程序基于什么平台运行?具体指的是基础类库和运行时,在 ASP.NET Core 1.0 RC2 应用程序中的体现,就是project.json中的frameworks配置,如果你的应用程序是基于 Desktop CLR 运行,frameworks配置为net461,那么你开发的程序包并不需要对应升级,只是相应的把其他需要升级到 RC2 版本的程序包对应升级下,但如果需要跨平台(基于 CoreCLR/CoreFx),那么所有的程序包都需要进行升级。

首先,我们需要把所有程序集的project.json配置,由原有的(示例):

{
  "version": "1.0.0-*",
  "description": "CNBlogs.Ad.Domain.Entities Class Library",
  "authors": [ "xishuai" ],
  "tags": [ "" ],
  "projectUrl": "",
  "licenseUrl": "",
  "frameworks": {
    "net451": { }
  },
  "dependencies": {
    "EntityFramework.Core": "7.0.0-rc2-16432"
  }
}

修改为(示例):

{
  "version": "1.0.0-*",
  "description": "CNBlogs.Ad.Domain.Entities Class Library",
  "authors": [ "xishuai" ],
  "frameworks": {
    "netcoreapp1.0": {
      "imports": ["dnxcore50", "portable-net45+win8"]
    }
  },
  "dependencies": {
    "Microsoft.EntityFrameworkCore": "1.0.0-rc2-final"
  }
}

上面主要是frameworks的配置更改,并且去除了tagsprojectUrllicenseUrlnetcoreapp1.0的前身是dnxcore50,意思是跨平台,为什么需要需要添加imports配置?主要是为了兼容,详细看上面的那篇文章。

frameworks的配置修改好之后,下面就是程序包的版本升级了,我大概记录了一些:

Old Version New Version
"EntityFramework.Core": "7.0.0-rc2-16432" "Microsoft.EntityFrameworkCore": "1.0.0-rc2-final"
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc2-16432" "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0-rc2-final"
"Microsoft.AspNet.Authentication.Cookies": "1.0.0-rc2-16160" "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-rc2-final"
"Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc2-15874" "Microsoft.AspNetCore.DataProtection.Extensions": "1.0.0-rc2-final"
"Microsoft.AspNet.Diagnostics": "1.0.0-rc2-16303" "Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final"
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc2-15994" "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final"
"Microsoft.AspNet.Mvc": "6.0.0-rc2-16614" "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final"
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc2-16614" "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0-rc2-final"
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc2-16156" "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final"
"Microsoft.AspNet.StaticFiles": "1.0.0-rc2-16036" "Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final"
"Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc2-15905" "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final"
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-15905" "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final"
"Microsoft.Extensions.Logging": "1.0.0-rc2-15907" "Microsoft.Extensions.Logging": "1.0.0-rc2-final"
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-15907" "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final"
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-15907" "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final"
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc2-16142", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc2-final"

还有一些其他的后面再说下,我们可以先更改代码,然后根据 VS 2015 的提示“安装对应程序包”功能,可以很方便的进行升级,如图:

1.1. Serializable 引用找不到

frameworks修改为netcoreapp1.0之后,Serializable的引用找不到了。

解决方式:添加"System.Runtime.Serialization.Formatters": "4.0.0-rc3-24113-00"程序包。

程序包源为 https://dotnet.myget.org/F/dotnet-core,而不是 http://nuget.cnitblog.com/nuget/core

1.2. EntityFramework 代码更改

原先代码:

public class EFDbContext : DbContext, IDbContext
{
    public EFDbContext(DbContextOptions options)
    : base(options)
    { }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddEntityFramework()
         .AddSqlServer()
         .AddDbContext<EFDbContext>(options => options.UseSqlServer(Configuration["data:ConnectionString"]));
}

修改为:

public class EFDbContext : DbContext, IDbContext
{
    public EFDbContext(DbContextOptions<EFDbContext> options)
    : base(options)
    { }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<EFDbContext>(options =>
        options.UseSqlServer(Configuration["data:ConnectionString"]));
}

如果程序集中引用了"Microsoft.EntityFrameworkCore": "1.0.0-rc2-final"程序包,需要将frameworks修改如下:

  "frameworks": {
    "netcoreapp1.0": {
      "imports": ["dnxcore50", "portable-net45+win8"]
    }
  }

否则会出现下面的错误:

1.3. xUnit 单元测试更改

Getting started with xUnit.net (.NET Core / ASP.NET Core)

Microsoft.AspNet.TestHost程序包,在 .NET Core RC2 版本被移除了,所以我们没办法对 WebApi 进行测试了(应该有其他的解决方式,待研究),原有的写法(虽然还是可以引用1.0.0-rc1-final版本,但和 RC2 已经不兼容了):

namespace CNBlogs.Ad.BaseTests
{
    public class BaseWebApiTest
    {
        protected TestServer _server;

        public BaseWebApiTest()
        {
            _server = TestServer.Create(app =>
            {
                var env = app.ApplicationServices.GetRequiredService<IHostingEnvironment>();
                var appEnv = app.ApplicationServices.GetRequiredService<IApplicationEnvironment>();
                var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
                new CNBlogs.Ad.WebApi.Startup(env, appEnv).Configure(app, env, loggerFactory);
            }, services =>
            {
                var connectionString = @"";
                services.AddMvc();
                services.Configure(connectionString);
            });
        }
    }
}

xUnit 单元测试的相关代码,基本上不需要更改,我们只需要更新下project.json的配置,原先配置:

{
  "version": "1.0.0-*",
  "description": "CNBlogs.Ad.BaseTests Class Library",
  "authors": [ "xishuai" ],
  "tags": [ "" ],
  "projectUrl": "",
  "licenseUrl": "",

  "frameworks": {
    "dnx451": { }
  },
  "dependencies": {
    "CNBlogs.Ad.WebApi": "1.0.0-*",
    "CNBlogs.Ad.Infrastructure": "1.0.0-*",
    "CNBlogs.Ad.Infrastructure.Interfaces": "1.0.0-*",
    "EntityFramework.Core": "7.0.0-rc2-16432",
    "xunit": "2.1.0",
    "xunit.runner.dnx": "2.1.0-rc1-build204",
    "Microsoft.AspNet.TestHost": "1.0.0-rc2-16032"
  },
  "commands": {
    "test": "xunit.runner.dnx"
  }
}

修改为:

{
  "version": "1.0.0-*",
  "description": "CNBlogs.Ad.BaseTests Class Library",
  "authors": [ "xishuai" ],
  "testRunner": "xunit",
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0-rc2-3002702"
        }
      },
      "imports": [ "dnxcore50", "portable-net45+win8" ]
    }
  },
  "dependencies": {
    "CNBlogs.Ad.Bootstrapper": "1.0.0-*",
    "CNBlogs.Ad.WebApi": "1.0.0-*",
    "CNBlogs.Ad.Infrastructure": "1.0.0-*",
    "CNBlogs.Ad.Infrastructure.Interfaces": "1.0.0-*",
    "Microsoft.EntityFrameworkCore": "1.0.0-rc2-final",
    "Microsoft.AspNet.TestHost": "1.0.0-rc1-final",
    "xunit": "2.1.0",
    "dotnet-test-xunit": "1.0.0-rc2-build10015"
  }
}

除了使用 VS 2015 的 Test Explorer 跑单元测试之外,我们还可以使用新的dotnet test命令。

1.4. Web 和 WebApi 代码更改

增加Program.cs

public class Program
{
  public static void Main(string[] args)
  {
      var host = new WebHostBuilder()
          .UseKestrel()
          .UseContentRoot(Directory.GetCurrentDirectory())
          .UseIISIntegration()
          .UseStartup<Startup>()
          .Build();

      host.Run();
  }
}

并添加web.config(移除wwwroot目录下的web.config):

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <!--
    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
  -->

  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
  </system.webServer>
</configuration>

之前引用的"Serilog.Framework.Logging": "1.0.0-*"日志组件,现在已被弃用,需要重新引用:

"Serilog": "2.0.0-rc-563",
"Serilog.Extensions.Logging": "1.0.0-rc2-10104",
"Serilog.Sinks.RollingFile": "2.0.0-rc-703"

日志代码配置:

public Startup(IHostingEnvironment env)
{
    // Set up configuration sources.
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddEnvironmentVariables();
    Configuration = builder.Build();

    Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Error()
    .WriteTo.RollingFile(Path.GetFullPath("logs/log-{Date}.txt"))
    .CreateLogger();
}

去除了之前的IApplicationEnvironment appEnv参数,因为现在Startup已不支持,如果使用的话,会直接抛出异常,WriteTo.RollingFile是日志写入的方式,现在Serilog都进行拆分了各种程序包,以Serilog.Sinks.XXXX的形式,详情查看:https://github.com/serilog

appsettings.json文件中的Logging: LogLevel配置更改:

Old Levels New Levels
Critical Critical
Error Error
Warning Warning
Information Information
Verbose Debug
Debug Trace

移除app.UseIISPlatformHandler();配置。

_ViewImports.cshtml文件中的:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers`

修改为:

@addTagHelper *, Microsoft.AspNet.Mvc.TagHelpers`

并引用"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0-rc2-final"程序包。

CookieAuthentication配置更新为:

var dataProtection = Microsoft.AspNetCore.DataProtection.DataProtectionProvider.Create(
    new DirectoryInfo(@"C:\shared-auth-ticket-keys"), x => x.SetApplicationName("XXXXX"));
var cookieOptions = new CookieAuthenticationOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    CookieHttpOnly = true,
    ExpireTimeSpan = TimeSpan.FromMinutes(43200),
    CookieName = ".XXXXX",
    CookiePath = "/",
    DataProtectionProvider = dataProtection
};
app.UseCookieAuthentication(cookieOptions);

完整的Startup.cs代码:

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using CNBlogs.Ad.Bootstrapper;
using Serilog;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Serilog.Sinks.RollingFile;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using AutoMapper

namespace CNBlogs.Ad.Web
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            // Set up configuration sources.
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();
            Configuration = builder.Build();

            Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Error()
            .WriteTo.RollingFile(Path.GetFullPath("logs/log-{Date}.txt"))
            .CreateLogger();
        }

        public IConfigurationRoot Configuration { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            services.AddDbContext<EFDbContext>(options =>
                options.UseSqlServer(Configuration["data:ConnectionString"]));
            
            services.AddEnyimMemcached();

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<CNBlogs.Ad.Domain.Entities.AdText, AdTextDTO>();
            });
            services.AddTransient<IUnitOfWork, UnitOfWork>();
            services.AddScoped<IDbContext, EFDbContext>();
            services.AddTransient<IUserService, UserService>();

            services.AddTransient<IAdTextRepository, AdTextRepository>();

            services.AddAuthentication();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddSerilog();

            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();

            var dataProtection = Microsoft.AspNetCore.DataProtection.DataProtectionProvider.Create(
                new DirectoryInfo(@"C:\shared-auth-ticket-keys"), x => x.SetApplicationName("XXXX"));
            var cookieOptions = new CookieAuthenticationOptions
            {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                CookieHttpOnly = true,
                ExpireTimeSpan = TimeSpan.FromMinutes(43200),
                CookieName = "XXXX",
                CookiePath = "/",
                DataProtectionProvider = dataProtection
            };
            app.UseCookieAuthentication(cookieOptions);

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "Default",
                    template: "{controller}/{action}/{id?}");
            });
        }
    }
}

2. 发布程序

首先,ASP.NET Core 1.0 RC2 的发布有两种方式:

  • Portable:便携式,发布不包含运行时文件,服务器需要安装 .NET Core。
  • Self-Contained:携带式,发布包含运行时文件,服务器不需要安装 .NET Core。

默认是 Portable 的发布方式,和 ASP.NET 5 RC1 所不同的是,ASP.NET Core 1.0 RC2 只能通过dotnet publish的命令进行发布。

project.json配置代码:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "gcServer": true
  },

  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    }
    //....
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
      "version": "1.0.0-*",
      "imports": "portable-net45+win8+dnxcore50"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [ "dnxcore50", "portable-net45+win8" ]
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

dotnet publish 命令发布:

发布生成目录为:\CNBlogs.Ad.Web\bin\Debug\netcoreapp1.0\publish,目录结构:

runtimes目录下有各个平台的native文件,其作用就是即时编译,publish目录除了基本的程序集之外,并没有运行时的文件。

根目录下的web.config配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!--
    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
  -->
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\CNBlogs.Ad.Web.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
  </system.webServer>
</configuration>

如果是 Self-Contained 方式发布,我们需要在project.json文件中添加runtimes配置(参考:Types of portability in .NET Core):

"runtimes": {
    "win10-x64": {},
    "osx.10.11-x64": {}
}

并去除dependencies程序包的platform配置:

"Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"//去除
    }

dotnet publish 命令重新发布,目录结构:

和上面的 Portable 发布不同的是,发布文件中多了coreclr.dll等,并且由CNBlogs.Ad.WebApi.dll变成了CNBlogs.Ad.WebApi.exe,我们甚至直接可以点击CNBlogs.Ad.WebApi.exe运行网站,根目录下的web.config配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!--
    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
  -->
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath=".\CNBlogs.Ad.WebApi.exe" arguments="" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
  </system.webServer>
</configuration>

如果是 Self-Contained 方式发布,服务器只需要安装 ASP.NET Core Module,相关文档:https://github.com/aspnet/IISIntegration/issues/105

如果是 Portable 方式发布,服务器则需要安装 .NET Core RC2

IIS 和之前的 ASP.NET 5 RC1 发布配置一样,创建 Web 站点,然后绑定发布目录就行,应用程序池的模式需要改为“无代码托管”。

posted @ 2016-05-27 12:07  田园里的蟋蟀  阅读(3224)  评论(12编辑  收藏  举报