ASP.NET から ASP.NET Core への移行

著者: Isaac Levin

この記事は、ASP.NET アプリを ASP.NET Core に移行するための参考ガイドです。

.NET Upgrade Assistant は、ASP.NET を ASP.NET Core に移行するのに役立つコマンド ライン ツールです。 詳細については、「.NET アップグレード アシスタントの概要」および「.NET アップグレード アシスタントを使用して ASP.NET MVC アプリを .NET 6 にアップグレードする」を参照してください。

包括的な移植のガイドについては、「既存の ASP.NET アプリを .NET Core に移植する」の電子書籍版を参照してください。

必須コンポーネント

.NET Core SDK 2.2 以降

ターゲット フレームワーク

ASP.NET Core プロジェクトを使うと、開発者は、.NET Core と .NET Framework のどちらか一方または両方を対象にして柔軟に開発できます。 最も適切なターゲット フレームワークの決定については、「サーバー アプリ用 .NET Core と .NET Framework の選択」をご覧ください。

.NET Framework を対象にする場合は、プロジェクトで個々の NuGet パッケージを参照する必要があります。

.NET Core を対象にすると、ASP.NET Core メタパッケージのおかげで、さまざまな明示的パッケージ参照をしなくて済みます。 Microsoft.AspNetCore.App メタパッケージをプロジェクトにインストールします。

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

メタパッケージを使うと、メタパッケージ内で参照されているパッケージはアプリでは展開されません。 .NET Core ランタイム ストアにはこれらのアセットが含まれており、パフォーマンス向上のためにプリコンパイルされています。 詳細については、ASP.NET Core 用の Microsoft.AspNetCore.App メタパッケージに関する記事を参照してください。

プロジェクトの構造の違い

ファイル形式は .csproj 、ASP.NET Core で簡略化されています。 いくつかの重要な変更は次のとおりです。

  • ファイルがプロジェクトの一部と見なされるためにファイルを明示的に含める必要はありません。 これにより、大規模なチームで作業する場合に XML のマージが競合するリスクが軽減されます。

  • 他のプロジェクトを GUID で参照することはなくなり、ファイルの読みやすさが向上します。

  • Visual Studio でアンロードせずにファイルを編集することができます。

    Edit CSPROJ context menu option in Visual Studio 2017

Global.asax ファイルの置換

ASP.NET Core では、アプリをブートストラップする新しいメカニズムが導入されました。 ASP.NET アプリケーションのエントリ ポイントは、Global.asax ファイルです。 ルート構成、フィルター、領域の登録などのタスクは、Global.asax ファイルで処理されます。

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

このアプローチでは、アプリケーションとその展開先のサーバーが、実装を妨げるような方法で結合されます。 結合を切り離すため、複数のフレームワークを一緒に使うさらにクリーンな方法を提供する OWIN が導入されました。 OWIN は、必要なモジュールのみを追加するためのパイプラインを提供します。 ホスティング環境は、Startup 関数を取得して、サービスとアプリの要求パイプラインを構成します。 Startup は、ミドルウェアのセットをアプリケーションに登録します。 アプリケーションは、要求ごとに、既存のハンドラーのセットに対するリンク リストのヘッド ポインターを指定して、各ミドルウェア コンポーネントを呼び出します。 各ミドルウェア コンポーネントは、要求処理パイプラインに 1 つ以上のハンドラーを追加できます。 これは、新しいリストのヘッドであるハンドラーへの参照を返すことによって行われます。 各ハンドラーは、リスト内の次のハンドラーを記憶して呼び出します。 ASP.NET Core では、アプリケーションへのエントリ ポイントは Startup であり、Global.asax に依存する必要はなくなりました。 .NET Framework で OWIN を使うときは、パイプラインとして次のようなものを使います。

using Owin;
using System.Web.Http;

namespace WebApi
{
    // Note: By default all requests go through this OWIN pipeline. Alternatively you can turn this off by adding an appSetting owin:AutomaticAppStartup with value “false”. 
    // With this turned off you can still have OWIN apps listening on specific routes by adding routes in global.asax file using MapOwinPath or MapOwinRoute extensions on RouteTable.Routes
    public class Startup
    {
        // Invoked once at startup to configure your application.
        public void Configuration(IAppBuilder builder)
        {
            HttpConfiguration config = new HttpConfiguration();
            config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional });

            config.Formatters.XmlFormatter.UseXmlSerializer = true;
            config.Formatters.Remove(config.Formatters.JsonFormatter);
            // config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;

            builder.UseWebApi(config);
        }
    }
}

これにより既定のルートが構成され、既定では Json 経由の XmlSerialization です。 必要に応じて、このパイプラインに他のミドルウェアを追加します (サービスの読み込み、構成設定、静的ファイルなど)。

ASP.NET Core は同様のアプローチを使いますが、エントリを処理するために OWIN には依存しません。 代わりに、これはメソッド (コンソール アプリケーションと同様) を通じて Program.csMain 行われ、 Startup そこに読み込まれます。

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Startup は、Configure メソッドを含む必要があります。 Configure では、必要なミドルウェアをパイプラインに追加します。 (既定の Web サイト テンプレートからの) 次の例では、拡張メソッドにより、以下をサポートするパイプラインが構成されます。

  • エラー ページ
  • HTTP Strict Transport Security
  • HTTP への HTTP リダイレクト
  • ASP.NET Core MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseMvc();
}

ホストとアプリケーションは切り離されており、将来別のプラットフォームに柔軟に移動できます。

注意

ASP.NET Core のスタートアップとミドルウェアについて詳しくは、「ASP.NET Core でのアプリケーションのスタートアップ」をご覧ください

構成を保存する

ASP.NET では保存の設定がサポートされています。 これらの設定は、たとえば、アプリケーションが展開された環境のサポートに使われます。 一般的な方法は、すべてのカスタム キー/値ペアを、Web.config ファイルの <appSettings> セクションに保存するというものでした。

<appSettings>
  <add key="UserName" value="User" />
  <add key="Password" value="Password" />
</appSettings>

アプリケーションでは、System.Configuration 名前空間内の ConfigurationManager.AppSettings コレクションを使ってこれらの設定を読み取ります。

string userName = System.Web.Configuration.ConfigurationManager.AppSettings["UserName"];
string password = System.Web.Configuration.ConfigurationManager.AppSettings["Password"];

ASP.NET Core では、アプリケーションの構成データを任意のファイルに保存し、ミドルウェアのブートストラップの一部として読み込むことができます。 プロジェクト テンプレートで使用される既定のファイルは、 appsettings.json です。

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppConfiguration": {
    "UserName": "UserName",
    "Password": "Password"
  }
}

アプリケーション内のインスタンスにこのファイルを読み込むには、次の IConfiguration 手順を Startup.cs実行します。

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

アプリでは、Configuration から読み取って設定を取得します。

string userName = Configuration.GetSection("AppConfiguration")["UserName"];
string password = Configuration.GetSection("AppConfiguration")["Password"];

このアプローチにはプロセスをより堅牢にする拡張機能があります。たとえば、依存性の注入 (DI) を使ってサービスとこれらの値を読み込むことができます。 DI アプローチは、厳密に型指定された構成オブジェクトのセットを提供します。

// Assume AppConfiguration is a class representing a strongly-typed version of AppConfiguration section
services.Configure<AppConfiguration>(Configuration.GetSection("AppConfiguration"));

注意

ASP.NET Core の構成について詳しくは、「ASP.NET Core の構成」をご覧ください。

ネイティブな依存性の注入

大規模で拡張性の高いアプリケーションを構築するときの重要な目標は、コンポーネントとサービスの疎な結合です。 依存性の注入はこれを実現するための一般的な手法であり、ASP.NET Core のネイティブなコンポーネントです。

ASP.NET アプリでは、開発者はサードパーティのライブラリに依存して依存性の注入を実装します。 そのようなライブラリの 1 つは、Microsoft Patterns & Practices によって提供される Unity です。

Unity で依存性の注入を設定する例は、UnityContainer をラップする IDependencyResolver の実装です。

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return new List<object>();
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

UnityContainer のインスタンスを作成し、サービスを登録して、HttpConfiguration の依存関係リゾルバーをコンテナー用の UnityResolver の新しいインスタンスに設定します。

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

必要な場所に IProductRepository を挿入します。

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

依存関係の挿入は ASP.NET Core の一部であるため、次のStartup.cs方法でサービスをConfigureServices追加できます。

public void ConfigureServices(IServiceCollection services)
{
    // Add application services.
    services.AddTransient<IProductRepository, ProductRepository>();
}

Unity でそうであったように、リポジトリは任意の場所に挿入できます。

注意

依存関係の挿入について詳しくは、「依存関係の挿入」をご覧ください。

静的ファイルの提供

Web 開発の重要な部分は、静的なクライアント側アセットを提供する機能です。 静的なファイルの最も一般的な例は、HTML、CSS、Javascript、およびイメージです。 これらのファイルは、アプリ (または CDN) の公開された場所に保存され、要求によって読み込めるように参照される必要があります。 このプロセスは、ASP.NET Core で変更されました。

ASP.NET では、静的ファイルはさまざまなディレクトリに保存され、ビューで参照されます。

ASP.NET Core では、構成が変更されていない限り、静的ファイルは "Web ルート" ( <コンテンツ ルート>/wwwroot) に保存されます。 ファイルは、Startup.Configure から UseStaticFiles 拡張メソッドを呼び出すことによって、要求パイプラインに読み込まれます。

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
}

注意

.NET Framework を対象にする場合は、NuGet パッケージ Microsoft.AspNetCore.StaticFiles をインストールします。

たとえば、wwwroot/images フォルダー内のイメージ アセットには、ブラウザーから http://<app>/images/<imageFileName> などの場所でアクセスできます。

注意

ASP.NET Core での静的ファイルの提供について詳しくは、静的ファイルに関するページをご覧ください。

複数値の cookie

複数値の cookie は ASP.NET Core ではサポートされていません。 値ごとに cookie を 1 つ作成します。

認証 cookie は ASP.NET Core では圧縮されません

セキュリティ上の理由から、認証 cookie は ASP.NET Core では圧縮されません。 認証 cookie を使用する場合、開発者は、含まれる要求情報の数を最小限に抑える必要があります。

部分的なアプリの移行

部分的にアプリを移行する方法の 1 つは、IIS サブアプリケーションを作成し、アプリの URL 構造を維持しながら ASP.NET 4.x から ASP.NET Core に特定のルートのみを移動することです。 たとえば、applicationHost.config ファイルからのアプリの URL 構造について考えてみます。

<sites>
    <site name="Default Web Site" id="1" serverAutoStart="true">
        <application path="/">
            <virtualDirectory path="/" physicalPath="D:\sites\MainSite\" />
        </application>
        <application path="/api" applicationPool="DefaultAppPool">
            <virtualDirectory path="/" physicalPath="D:\sites\netcoreapi" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:" />
            <binding protocol="https" bindingInformation="*:443:" sslFlags="0" />
        </bindings>
    </site>
	...
</sites>

ディレクトリの構造:

.
├── MainSite
│   ├── ...
│   └── Web.config
└── NetCoreApi
    ├── ...
    └── web.config

[BIND] と入力フォーマッタ

以前のバージョンの ASP.NET では、過剰ポスティング攻撃からの保護に [Bind] 属性が使用されていました。 入力フォーマッタは ASP.NET Core では動作が異なります。 この [Bind] 属性は、入力フォーマッタと共に ON または XML を解析 JSするときにオーバーポストを防ぐよう設計されなくなりました。 データのソースが x-www-form-urlencoded コンテンツ タイプでポストされたフォーム データである場合、これらの属性はモデル バインドに影響します。

ON 情報をコントローラーに投稿 JSし、ON 入力フォーマッタを使用 JSしてデータを解析するアプリの場合は、属性を [Bind] 、属性で定義されたプロパティと一致するビュー モデルに [Bind] 置き換えることをお勧めします。

その他の技術情報