Migration von ASP.NET zu ASP.NET Core

Von Isaac Levin

Dieser Artikel dient als Leitfaden zum Migrieren von ASP.NET-Anwendungen zu ASP.NET Core. Im E-Book Portieren vorhandener ASP.NET-Apps auf .NET Core finden Sie einen verständlichen Leitfaden zur Portierung.

Voraussetzungen

.NET Core SDK 2.2 oder höher

Zielframeworks

ASP.NET Core-Projekte bieten Entwicklern die Flexibilität, Anwendungen für .NET Core, .NET Framework oder für beide Frameworks zu erstellen. Informationen zur Auswahl eines geeigneten Frameworks finden Sie unter Wahl zwischen .NET Core und .NET Framework für Server-Apps.

Bei der Erstellung von Anwendungen für .NET Framework müssen Projekte auf einzelne NuGet-Pakete verweisen.

Wenn das Zielframework .NET Core ist, können Sie mit dem Metapaket für ASP.NET Core auf die meisten expliziten Paketverweise verzichten. Das Microsoft.AspNetCore.App-Metapaket können Sie folgendermaßen in Ihrem Projekt installieren:

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

Wenn das Metapaket verwendet wird, werden mit der Anwendung keine Pakete bereitgestellt, auf die im Metapaket verwiesen wird. Die notwendigen Objekte sind im .NET Core-Laufzeitspeicher vorhanden und werden zur Verbesserung der Leistung vorkompiliert. Weitere Informationen finden Sie unter Das Metapaket „Microsoft.AspNetCore.App“ für ASP.NET Core 2.1.

Unterschiede bei Projektstrukturen

Das Dateiformat .csproj wurde in ASP.NET Core vereinfacht. Zu einigen wichtigen Änderungen gehören folgende Punkte:

  • Dateien müssen nicht explizit eingebunden werden, um als Teil des Projekts behandelt zu werden. Dadurch wird in großen Entwicklerteams das Risiko von Konflikten beim Zusammenführen von XML-Dateien reduziert.

  • Auf GUID-Verweise zu anderen Projekten wird verzichtet, wodurch die Lesbarkeit von Dateien erhöht wird.

  • Die Datei kann bearbeitet werden, ohne in Visual Studio entladen zu werden:

    Kontextmenüoption „Edit CSPROJ“ (CSPROJ-Datei bearbeiten) in Visual Studio 2017

Ersetzen der Datei „Global.asax“

Mit ASP.NET Core wurde ein neuer Mechanismus für den Bootstrap einer Anwendung eingeführt. Der Einstiegspunkt für ASP.NET-Anwendungen ist die Datei Global.asax. In der Datei Global.asax werden Aufgaben wie die Routenkonfiguration, die Einrichtung von Filtern und Bereichsregistrierungen bearbeitet.

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

Bei diesem Ansatz werden die Anwendung und der Server, auf dem die Anwendung bereitgestellt wird, so miteinander gekoppelt, dass es zu Konflikten mit der Implementierung kommt. OWIN wurde mit dem Ziel eingeführt, beide Komponenten zu entkoppeln und so mehrere Frameworks leichter gemeinsam verwenden zu können. OWIN stellt eine Pipeline zur Verfügung, über die nur die benötigten Module hinzugefügt werden. Die Hostingumgebung verwendet eine Startup-Funktion, um Dienste und die Anforderungspipeline der Anwendung zu konfigurieren. Startup registriert die Middleware bei der Anwendung. Bei jeder Anforderung ruft die Anwendung alle Middlewarekomponenten auf, wobei der Hauptzeiger einer verknüpften Liste auf die vorhandenen Handler zeigt. Jede Middlewarekomponente kann einen oder mehrere Handler zur Anforderungspipeline hinzufügen. Möglich wird dies, indem ein Verweis auf den Handler zurückgegeben wird, der zum neuen ersten Element der Liste wird. Jeder Handler ist dafür verantwortlich, sich den nächsten Handler in der Liste zu merken und diesen aufzurufen. In ASP.NET Core ist Startup der Einstiegspunkt für eine Anwendung. Eine Abhängigkeit von Global.asax ist nicht mehr vorhanden. Wenn Sie OWIN mit .NET Framework verwenden möchten, können Sie sich beispielsweise an folgendem Code für die Pipeline orientieren:

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);
        }
    }
}

Hierdurch werden Ihre Standardrouten konfiguriert. Außerdem wird standardmäßig XmlSerialization anstelle von JSON verwendet. Bei Bedarf können Sie weitere Middleware (z.B. zum Laden von Diensten, für Konfigurationseinstellungen, für statische Dateien usw.) zur Pipeline hinzufügen.

ASP.NET Core verwendet einen ähnlichen Ansatz, ist jedoch hinsichtlich des Einstiegspunkts nicht auf OWIN angewiesen. Stattdessen kommt – ähnlich wie bei Konsolenanwendungen – in Program.cs die Main-Methode zum Einsatz, in der Startup geladen wird.

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>();
    }
}

In Startup muss die Configure-Methode enthalten sein. Fügen Sie in Configure der Pipeline die erforderliche Middleware hinzu. Im folgenden Beispiel, das der Standardwebsitevorlage entnommen wurde, konfigurieren Erweiterungsmethoden die Pipeline mit Unterstützung für Folgendes:

  • Fehlerseiten
  • HTTP Strict Transport Security
  • HTTP-Umleitung zu HTTPS
  • ASP.NET Core MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

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

Durch die Entkopplung von Host und Anwendung wird die Möglichkeit geschaffen, in der Zukunft eine Migration zu einer anderen Plattform vorzunehmen.

Hinweis

Ausführliche Informationen zum Start einer Anwendung in ASP.NET Core und zu Middleware finden Sie unter Startup in ASP.NET Core (Starten von Anwendungen in ASP.NET Core).

Speicherkonfigurationen

ASP.NET unterstützt das Speichern von Einstellungen. Diese Einstellungen dienen z.B. der Unterstützung der Umgebung, in der die Anwendungen bereitgestellt werden. Häufig werden alle benutzerdefinierten Schlüssel-Wert-Paare im Abschnitt <appSettings> der Datei Web.config gespeichert:

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

Anwendungen lesen diese Einstellungen über die ConfigurationManager.AppSettings-Auflistung im System.Configuration-Namespace aus:

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

ASP.NET Core kann Konfigurationsdaten der Anwendung in einer beliebigen Datei speichern und diese während des Middleware-Bootstraps laden. Die in Projektvorlagen verwendete Standarddatei ist appsettings.json :

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

Diese Datei wird in Ihrer Anwendung in eine Instanz von IConfiguration in Startup.cs geladen:

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

public IConfiguration Configuration { get; }

Die Anwendung liest aus Configuration, um die Einstellungen abzurufen:

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

Dieser Ansatz kann erweitert werden, um einen noch stabileren Prozess zu gewährleisten. Beispielsweise kann über die Abhängigkeitsinjektion ein Dienst mit diesen Werten geladen werden. Durch die Abhängigkeitsinjektion wird eine Reihe stark typisierter Konfigurationsobjekte zur Verfügung gestellt.

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

Hinweis

Ausführliche Informationen zur ASP.NET Core-Konfiguration finden Sie unter Configuration in ASP.NET Core (Konfiguration in ASP.NET Core).

Native Abhängigkeitsinjektion

Ein wichtiges Ziel bei der Erstellung großer, skalierbarer Anwendungen besteht in der losen Kopplung von Komponenten und Diensten. Die Abhängigkeitsinjektion ist hierfür eine beliebte Methode und eine native Komponente von ASP.NET Core.

Zur Implementierung der Dependency Injection greifen Entwickler in ASP.NET-Anwendungen auf Bibliotheken von Drittanbietern zurück. Eine solche Bibliothek ist Unity, die von Microsoft Patterns & Practices bereitgestellt wird.

Ein Beispiel für das Einrichten der Abhängigkeitsinjektion mit Unity ist die Implementierung der Schnittstelle IDependencyResolver, die einen UnityContainer umschließt:

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();
    }
}

Erstellen Sie eine Instanz von UnityContainer, registrieren Sie den Dienst, und weisen Sie für Ihren Container den Abhängigkeitskonfliktlöser von HttpConfiguration der neuen Instanz von UnityResolver zu:

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.
}

Fügen Sie bei Bedarf IProductRepository ein:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

    // Other controller methods not shown.
}

Da die Abhängigkeitsinjektion eine Komponente von ASP.NET Core ist, können Sie Ihren Dienst in Startup.cs der Methode ConfigureServices hinzufügen:

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

Genau wie bei Unity kann auch hier das Repository an einer beliebigen Stelle eingefügt werden.

Hinweis

Weitere Informationen zur Dependency Injection finden Sie unter Dependency Injection.

Bereitstellen statischer Dateien

Ein wichtiger Teil der Webentwicklung ist die Möglichkeit, statische, clientseitige Objekte bereitzustellen. Die häufigsten Beispiele für statische Dateien sind HTML-, CSS-, JavaScript- und Bilddateien. Diese Dateien müssen am veröffentlichten Speicherort der Anwendung (oder des CDN) gespeichert werden. Außerdem muss auf diese verwiesen werden, damit sie von einer Anforderung geladen werden können. Dieser Prozess wurde in ASP.NET Core geändert.

Statische Dateien werden in ASP.NET in verschiedenen Verzeichnissen gespeichert. Der Verweis auf die Dateien erfolgt in den Ansichten.

In ASP.NET Core werden statische Dateien im Webstammverzeichnis ( <content root>/wwwroot) gespeichert, falls keine anderen Einstellungen vorgenommen wurden. Die Dateien werden über den Aufruf der Erweiterungsmethode UseStaticFiles aus Startup.Configure in die Anforderungspipeline geladen:

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

Hinweis

Wenn Sie Anwendungen für .NET Framework entwickeln, installieren Sie das NuGet-Paket Microsoft.AspNetCore.StaticFiles.

Beispielsweise kann ein Browser an einem Speicherort wie http://<app>/images/<imageFileName> auf ein Bildobjekt im Ordner wwwroot/images zugreifen.

Hinweis

Ausführliche Informationen zum Bereitstellen statischer Dateien in ASP.NET Core finden Sie im Artikel zu statischen Dateien.

Mehrwertige cookies

Mehrwertige cookies werden in ASP.NET Core nicht unterstützt. Erstellen Sie ein cookie pro Wert.

Die cookies zur Authentifizierung sind in ASP.NET Core nicht komprimiert.

Aus Sicherheitsgründen sind Authentifizierungscookies (cookies) nicht in ASP.NET Core komprimiert. Wenn Sie cookies zur Authentifizierung verwenden, sollten Entwickler die Anzahl von enthaltenen Anspruchsinformationen möglichst gering halten.

Partielle App-Migration

Ein Ansatz für die partielle App-Migration besteht darin, eine IIS-Unteranwendung zu erstellen und nur bestimmte Routen von ASP.NET 4.x zu ASP.NET Core zu verschieben, wobei die URL-Struktur der App beibehalten wird. Betrachten Sie beispielsweise die URL-Struktur der Anwendung aus der Datei applicationHost.config:

<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>

Verzeichnisstruktur:

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

[Bind] und Eingabeformatierer

In früheren Versionen von ASP.NET wurde das [Bind]-Attribut zum Schutz vor Overpostingangriffen verwendet. Eingabeformatierer funktionieren in ASP.NET Core unterschiedlich. Das [Bind]-Attribut ist nicht mehr darauf ausgelegt, Overposting zu verhindern, wenn es mit Eingabeformatierern für die Verarbeitung von JSON- oder XML-Dateien verwendet wird. Diese Attribute wirken sich auf die Modellbindung aus, wenn es sich bei den Quelldaten um Formulardaten handelt, die mit dem Inhaltstyp x-www-form-urlencoded gepostet wurden.

Für Apps, die JSON-Informationen an Controller senden und JSON-Eingabeformatierer für die Verarbeitung der Daten verwenden, sollten Sie das [Bind]-Attribut durch ein Ansichtsmodell ersetzen, das den vom [Bind]-Attribut definierten Eigenschaften entspricht.

Zusätzliche Ressourcen