升级 asp.net core 1.1 到 2.0 preview

Upgrading to .NET Core 2.0 Preview

 

1 更新 依赖的类库 改为 标准库

2 web app  更改 csproj 文件---升级版本

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
 
</PropertyGroup>
 
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-preview1-final" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.0.1" />
</ItemGroup>
 

其他变动参考下面的文章,细节。

(visual studio code 编辑器出中文语言包了!?!?!?!??)

 

 

参考:https://weblog.west-wind.com/posts/2017/May/15/Upgrading-to-NET-Core-20-Preview

 

Over the weekend I spent a bit of time updating my AlbumViewer Sample Angular and ASP.NET Core Application to .NET Core 2.0. The good news is that for the most part this is another relatively smooth update, with some nice payoffs, but also a couple of pain points.

Album Viewer

Just to set the stage for those of you that haven't heard me blabber about my sample project before: I have an AlbumViewer sample application I've been carrying forward through all the versions of .NET Core (and Angular as well) from earlier previews to today. The album viewer is a smallish application of a .NET Core ASP.NET API to an Angular (4) front end application that's made up of four separate projects:

  • ASP.NET Core Web app that handles the Web API backend
  • .NET business object project that holds Repository and EF access code
  • .NET utility project with a few helpers
  • Angular front end Web Site Project (discussed in detail in my previous update post)

Although the project is relatively small that could easily live in a single project, I broke out functionality into multiple projects to make it a little more realistic and exercise the the inter-project dependencies and different behaviors of Web and separate class library projects under .NET Core.

You can find the project on Github at:

and the running application (currently on 1.1 still):

The conversion for .NET Core 2 discussed in this post currently lives on a side GitHub branch NET_CORE_2_MIGRATION for the time being until there's more stable tooling available. As with the 1.1 upgrade, the .NET Core 2 features require the latest version of Visual Studio Preview in order to work. So the Master branch will continue to stay on version 1.1 of .NET Core that can run comfortably on current production tools and runtimes.

 

Getting started with .NET Core 2.0

In order to use the .NET Core 2.0 features you'll need to install a couple of things:

Visual Studio is optional if you want to work from the command line or you're not running on Windows. The SDK is the key piece you need.

Important!

Even if you install the Visual Studio Preview, you'll still need to install the .NET Core 2 SDK as the Preview 1 release of Visual Studio does not install it. This will change in the future, but for now - install it explicitly.

Upgrading to .NET Core 2.0 and .NET Standard 2.0 - mostly easy

So, the good news is that 99% of the upgrade to .NET Core was very easy and quick and boiled down to a few very simple steps:

  • Changing the target of the Class Library projects to .NET Standard 2.0
  • Changing the target of the Web API project to .NET Core App 2.0
  • In the Web Project adding a reference to Microsoft.AspNetCore.All
  • In the Web Project removing most other ASP.NET related Nuget references

And that's it! Let's go over those.

Changing the Target Framework

I used Visual Studio to change the Build target of each of the 3 projects. The Class Libraries are set to target .NET Standard 2.0, while the Web app targets .NET Core 2.0.

Here's the setting to .NET Standard 2.0 in the class library projects:

Set .NET Standard 2.0 Figure 1 - Setting .NET Standard 2.0 in Visual Studio's Build Settings for the class library projects

Making this change causes the TargetFramework to be changed to netstandard2.0 in the project's .csproj file.

If you're not using Visual Studio you can also make this change directly in the .csproj file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- *** THIS ONE *** -->
    <TargetFramework>netstandard2.0</TargetFramework>
    ...
  </PropertyGroup>
  ...
</Project>

In the Web App the settings look slightly different - there you have to use netcoreapp2.0 for the TargetFramework.

.NET Core Framework version in Web App Figure 2 - Configuring .NET Core version in a Web Project

and here are the settings for the .csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
  <PropertyGroup>
    <!-- *** THIS ONE *** -->
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <AssemblyName>AlbumViewerNetCore</AssemblyName>
    <OutputType>Exe</OutputType>
    <PackageId>AlbumViewerNetCore</PackageId>
    <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
  </PropertyGroup>
  ...
</Project>

Right - the madness of versions, SDKs, and runtimes continues. netcoreapp2.0 is an a concrete .NET version reference - .NET Core 2.0 specifically, while netstandard2.0 is used for class libraries that build a library that conforms to the .NET Standard specification. So currently the ASP.NET startup project is marked with netcoreapp while class libraries generally will target netstandard. If you're confused by this madness - you're not alone and I'll have more to say and the reasons for this at the end of this post.

Replacing ASP.NET Packages the consolidated Microsoft.AspNetCore.All Meta Package

In ASP.NET Core 2.0 Microsoft is providing a bundled ASP.NET Core package that includes most of the ASP.NET feature packages in a single reference bundle. This package is actually a placeholder that triggers import of the required child packages as needed when the application is compiled.

Here's what this looks like when you add Microsoft.AspNetCore.All to your project:

<Project Sdk="Microsoft.NET.Sdk.Web">
    ...
	<ItemGroup>
		<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-preview1-final" />

		<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
		<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />		
	</ItemGroup>
	<ItemGroup>
	  <ProjectReference Include="..\AlbumViewerBusiness\AlbumViewerBusiness.csproj" />
	</ItemGroup>

	<!-- *** Old References are Removed ***
	<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Cors" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />    
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.1" />    
    <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="1.1.1" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
  </ItemGroup>-->
  ...

Notice the large commented block: That's all the stuff I removed in favor of the Microsoft.AspNetCore.All meta package. There are no other Microsoft packages referenced directly in the Reference section - everything needed is coming in through the meta package.

You end up with a much cleaner looking project that looks a lot more sensible, with the stuff you are actually working with, rather than seeing a mess of plumbing dependencies that have nothing to do with the application code you're writing.

This is an overdue improvement. Nice!

You can see how this makes the project look much cleaner. Check out the project references in this screen shot from Visual Studio:

Figure 3 - A three project Web app with its dependencies highlighted

This is much cleaner than what we had especially prior to the move to .csproj and 1.1 and now even more so in 2.0. This is starting to look a lot more like classic .NET projects where you see only the references your code is referencing, which is what it should be.

Making these changes took all of 5 minutes and I was able to get my project compiled except for 2 issues.

Authentication Changes

This almost made my application compile. Yeah, but it never just works, right? All of the above worked except one compilation error related to Authentication.

 Figure 4 - Authentication config has changed in ASP.NET Core 2.0 and has to be updated. This code that worked in 1.1 in Configure() no longer works.

The Authentication provider in ASP.NET Core 2.0 has been refactored and the old way of configuration and accessing has been replaced by a slightly different configuration code path. The changes are relatively minor, but I got rather frustrated because there's no documentation yet and the stuff of course isn't discoverable in any way since it all comes in from DI.

In the AlbumViewer, I use very simple API endpoint based security. There's an account/login endpoint that validates a username and password and then sets an Auth Cookie with a ClaimsIdentity. As complex as the ASP.NET Identity system is, its saving grace is that you can still do simple things like this with little effort, effectively offloading the authorization to your own business logic and letting ASP.NET Core do the important part of encrypting the Claims based cookie and picking up the User Identity as part of the middleware processing. Doing this sort of thing requires very little code and is quite manageable.

Let's take a look at the old code and new code side by side. The logic of this code really doesn't change much at all, just the APIs called change a little.

 

Old Code (ASP.NET Core 1.1)

ConfigureServices() method

services.AddAuthentication();

Configure() method

 // Enable Cookie Auth with automatic user policy
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = false,
    LoginPath = "/api/login"
});

Login API Endpoint

[AllowAnonymous]                    
[HttpPost]
[Route("api/login")]
public async Task<bool> Login([FromBody]  User loginUser)
{            
    var user = await accountRepo.AuthenticateAndLoadUser(loginUser.Username, loginUser.Password);

    if (user == null)
        throw new ApiException("Invalid Login Credentials", 401);

    var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    identity.AddClaim(new Claim(ClaimTypes.Name, user.Username))    ;
   
    if (user.Fullname == null)
        user.Fullname = string.Empty;
    identity.AddClaim(new Claim("FullName", user.Fullname));

    await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
        new ClaimsPrincipal(identity));

    return true;
}

New Code (ASP.NET Core 2.0)

ConfigureServices() method

services.AddCookieAuthentication(o =>
{
	o.LoginPath = "/api/login";
	o.LogoutPath = "/api/logout";
});

Configure() method

app.UseAuthentication();

Login API Endpoint

[AllowAnonymous]                    
[HttpPost]
[Route("api/login")]
public async Task<bool> Login([FromBody]  User loginUser)
{            
    var user = await accountRepo.AuthenticateAndLoadUser(loginUser.Username, loginUser.Password);

    if (user == null)
        throw new ApiException("Invalid Login Credentials", 401);

    var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    identity.AddClaim(new Claim(ClaimTypes.Name, user.Username))    ;
   
    if (user.Fullname == null)
        user.Fullname = string.Empty;
    identity.AddClaim(new Claim("FullName", user.Fullname));

    // *** NOTE: SignInAsync is now on the HttpContext instance! ***
    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
        new ClaimsPrincipal(identity));

    return true;
}

As you can see, Microsoft decided to shuffle the deck chairs a little. The auth configuration has moved to ConfigureServices(). Options are now passed to the specific Authentication service declaration (AddCookieAuthentication()). If you're using a auth provider rather than Cookies like OpenID, Facebook, etc. there are methods for those specific configuration providers as extensions methods that have moved in the same way.

The Configure() method then simply attaches the authentication middleware using AddAuthentication() without further settings .

This makes a lot more sense. It's never sat right with me that some things are configured in the 'Configure()' method. It seems if you're going to use DI let all configuration happen at the ConfigureService() level and it looks like Microsoft is taking that approach to move configuration tasks into that common place. I suspect we'll see more of these types of refactorings in the future. Logging comes to mind which still has a bunch of settings set in Configure().

For the actual authentication implementation code there's only one very subtle change in the call to:

await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
        new ClaimsPrincipal(identity));

which changes to directly getting called on HttpContext object instead of the HttpContext.Authentication. Confusingly the HttpContext.Authentication instance still exists and even has a SignInAsync() method, but it no longer works correctly.

Visual Studio Tooling Pain - again

It looks like in Visual Studio 2017 and the .NET Core 2 preview the tooling once again has reverted to an unstable state. When first loading up the project, Visual Studio showed all sorts of errors in the code viewers and compiler errors, even though the code was compiling from the command line.

Check out this lovely screen shot I shared on Twitter on Friday:

Figure 4 - Visual Studio Tooling not resolving references

The terminal on the bottom is running the same application, while Visual Studio is flagging all sorts of reference errors and won't compile. Even compiling externally wouldn't help - only way to fix was to Close Solution, re-open Solution.

To get around this, I've been using Visual Studio Code and command line tooling lately because of the incessant pain that the Visual Studio is causing with .NET Core. However, when doing these migrations, it's certainly helpful to have Visual Studio automatically add framework and package references which is easier to do in the full IDE especially if you don't know what you're looking for.

.NET Standard 2.0 - Finally

With this release we finally get some .NET Core 2.0 bits that we can play with. One of the key reasons I've been excited about the .NET Core 2.0 update is support for .NET Standard 2.0 and the much expanded API it brings. .NET Standard 2.0 more than doubles the surface of the .NET Core 1.1 API and brings back a shitload of APIs that were missing and made it a lot more difficult than it should be to re-use full framework code in .NET Core.

I spent a bit of time over the weekend creating a few test .NET Core 2.0 projects and throwing some of my full framework assemblies at them. You can now easily add full framework assemblies to your .NET Core 2.0 projects and have a pretty good chance that the assembly will just work without major changes - assuming it doesn't use features that .NET Core doesn't provide. It turns out the delta between what works in full framework and .NET Core 2.0 got a lot smaller in .NET Core 2.0 and many common things just work.

 

Keep in mind that there are still quite a few features that are not supported in .NET Core. I ran afoul with a few data apis (DbProviderFactories and CreateDataAdapter for example) and most commonly the non-support of System.Configuration APIs which include .config file access and ConnectionStrings. Those particular features I have to work around in my libraries.

Also remember that failures won't bomb at compile time, but at runtime. This means you can use an assembly even if there are some features that don't work as long as you don't call into an execution path that accesses those.

For example, Westwind.Utilities has a boat load of general purpose helper functions. A very large percentage of that library will just work as is. The data apis, the string and file apis, most of the reflection tools and even the configuration bits once adjusted to not default to the .config provider actually worked. In fact in the AlbumViewer sample I could reference the full framework assembly now and replace the small subset project I created to provide the features I needed.

To me this has been the biggest reason to hold off with a .NET Core development because clearly the expanded API surface in .NET Core 2.0 will make it much easier to reuse existing code without any changes (if it's well factored code) or with some relatively minor re-configuration or code bracketing to avoid unsupported APIs.

Pick a Number, Any Number

Overall this framework update provides a relatively smooth upgrade path aside from a few breaking changes.

But one thing that is extremely frustrating is the proliferation of SDKs, Runtimes, Tools and all the different versions that they incorporate. Especially when those things end up not quite lining up because they are all in different stages of preview.

Let's see we have (with preview sub versions):

  • .NET Core 2.0
    The .NET Runtime Core version that runs the actual application. This can be 1.0, 1.1 or now 2.0. This is the concrete implementation that you run. This is really formalized by the netcoreapp2.0 setting in a startup project.

  • .NET Standard 2.0
    Is mainly used for class libraries where you can specify .NET Standard 2.0 as a target so that your assembly can be used in any .NET Standard compliant application. Think of it as creating an library that conforms to an interface/standard/specification. - netstandard2.0

  • .NET Core App 2.0
    Specifies a concrete version of the framework - .NET Core 2.0 in this case - that is to be run by an executable. ASP.NET 2.0 needs to use this due a problem with this preview, whereby ASP.NET couldn't bind to .NET Standard 2.0 as it uses some APIs that haven't been moved to Standard 2.0 yet. This will be fixed in future previews. This is also the reason that ASP.NET Core 2.0 currently can't run with the full framework runtime (which uses .NET Standard 2.0). If you're using .NET Core this has no real side effects, other than the different target name and again in the next Preview the Web will target .NET Standard and work with full framework again. - netcoreapp2.0

  • .NET Core SDK 2.0
    This is the SDK version that you use to build, run and publish your applications with - essentially everything surrounding the dotnet and the tooling required to make it work. Newer versions of this SDK support building apps of older versions back to 1.0 at this point. So if you install the newer SDK, you should be good to go. You can override the SDK explicitly by using global.json file that specifies an explicit SDK version. If not specified the highest version installed SDK is used.

Andrew Locke has a good post on the topics of runtimes and SDKs, but the mere fact we need to have blog posts trying to explain version numbers and how .NET Standard works in relation to the .NET runtime is a major failing of the platform IMHO.

That's a lot of different components to take in. As a developer I shouldn't have to care about most of these things. I should be able to pick a target (.NET Standard 2.0 or Net Core App) or multiples in the case of class libs, and the tooling should be able to figure out what I need to build an application that supports that target. And hopefully let me change those targets just as easily when new versions roll around.

Confucius say - Shut up and Dance!

To make things even more confusing the tooling does some crazy stuff in the latest version when it displays information about the SDK usage in a Visual Studio project:

Check out this ditty:


Figure 5 - .NET Standard 2.0 pointing at 1.1 platform packages. Okay dokey!

that seems to indicate I'm running version 1.1.0 of... something. When I first upgraded I kept thinking the upgrade didn't work even though my project had .NET Core 2.0 set for all projects.

Damien Edwards straightened me out on Twitter indicating that 1.1.0 has nothing to do with the .NET Standard version - it's simply one of the dependent packages. Ok fair enough, but why the heck is this showing at all? NetCorePlatforms? Packaging Tools? Why are these important when I don't even know what these are? It turns out that the current formatting in VS is missing the .NET Standard or .NET Core App designation on the NetStandard.Library node, which is an ordinary omission bug in this release but as to the other values that's a mystery...

It's not that .NET Core is really all that difficult - it really isn't. But when you see this salad of SDKs and versions plus the uhm... sub-par tooling experience when you first walk up to .NET Core I don't think the first experience is a positive one for the majority of developers.

Keeping out the stuff you don't need to see is key to that - it's fine that it's there to tinker with, but the base tooling should let developers focus on the stuff they need to work on and not the plumbing underneath.

Summary

While the tooling issues and version overload is annoying in a lot of ways, lets remember that this is a preview. I also think .NET Core is well worth some of the growing pains we're seeing. Once you're on your way building an application the process is not that different from what it used to be with full framework .NET that came before - especially now with .NET Core 2.0 and .NET Standard 2.0 providing a much richer baseline API that makes it much easier to use or adapt existing full framework code into .NET Core.

Hopefully the messaging around the various components will get a bit clearer and become more established so we don't have to run around with our heads cut off each time a new version comes out and we need to upgrade our projects.

Resources

this post created with Markdown Monster
posted @ 2017-06-15 21:25  特洛伊-Micro  阅读(602)  评论(0编辑  收藏  举报