Переход с ASP.NET к ASP.NET Core 2.0

  • Tutorial

Эта статья является переводом справочного руководства по переносу приложений из ASP.NET в ASP.NET Core 2.0. Ссылка на оригинал


Содержание


  1. Требования
  2. Выбор Фреймворка
  3. Различия в структуре проекта
  4. Замена Global.asax
  5. Хранение конфигураций
  6. Встроенный механизм Dependency Injection
  7. Работа со статическими файлами

Требования


• .NET Core 2.0.0 SDK или более поздняя версия.


Выбор фреймворка


Для работы с ASP.NET Core 2.0 проектом, разработчику предстоит сделать выбор – использовать .NET Core, .NET Framework или использовать сразу оба варианта. В качестве дополнительной информации можно использовать руководство Choosing between .NET Core and .NET Framework for server apps (вкратце можно сказать что .NET core является кроссплатформенной библиотекой, в отличие от .NET Framework) для того чтобы понять, какой Фреймворк для вас окажется наиболее предпочтительным.
После выбора нужного Фреймворка в проекте необходимо указать ссылки на пакеты NuGet.
Использование .NET Core позволяет устранить многочисленные явные ссылки на пакеты, благодаря объединенному пакету (мета пакету) ASP.NET Core 2.0. Так выглядит установка мета пакета Microsoft.AspNetCore.All в проект:


<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>

Различия в структуре проекта


Структура файла проекта .csproj была упрощена в ASP.NET Core. Вот некоторые значительные изменения:


• Явное указание файлов является необязательным для добавления их в проект. Таким образом, уменьшается риск конфликтов в процессе слияния XML, если над проектом работает большая команда
• Больше нет GUID ссылок на другие проекты, что улучшает читаемость
• Файл можно редактировать без его выгрузки из Visual Studio:




Замена Global.asax


Точкой входа для 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регистрирует набор промежуточных сервисов (middleware) вместе с приложением. Для каждого запроса приложение вызывает поочередно каждый из набора промежуточных сервисов, имеющих указатель на первый элемент связанного списка обработчиков.


Каждый компонент промежуточного сервиса может добавить один или несколько обработчиков в конвейер обработки запросов. Это происходит с помощью возврата ссылки на обработчик, который находится в начале списка.


И обработчик, закончив свою работу, вызывает следующий обработчик из очереди.
В ASP.NET Core, точкой входа в приложении является класс Startup, с помощью которого мы нивелируем зависимость от Global.asax.


Если изначально был выбран .NET Framework то при помощи OWIN мы можем сконфигурировать конвейер запросов как в следующем примере:


using Owin;
using System.Web.Http;

namespace WebApi
{
    // Заметка: По умолчанию все запросы проходят через этот конвейер OWIN. В качестве альтернативы вы можете отключить это, добавив appSetting owin: AutomaticAppStartup со значением «false».
    // При отключении вы все равно можете использовать приложения OWIN для прослушивания определенных маршрутов, добавив маршруты в файл global.asax с помощью MapOwinPath или расширений MapOwinRoute на RouteTable.Routes

    public class Startup
    {
        // Вызывается один раз при запуске для настройки вашего приложения.
        public void Configuration(IAppBuilder builder)
        {
            HttpConfiguration config = new HttpConfiguration();
//Здесь настраиваем маршруты по умолчанию, 
            config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional });
//Указываем на то что в качестве файла конфигурации мы будем использовать xml вместо json 

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

            builder.UseWebApi(config);
        }
    }
}

Также при необходимости здесь мы можем добавить другие промежуточные сервисы в этот конвейер (загрузка сервисов, настройки конфигурации, статические файлы и т.д.).
Что касается версии фреймворка .NET Core, то здесь используется подобный подход, но не без использования OWIN для определения точки входа. В качестве альтернативы используется метод Main в Program.cs (по аналогии с консольными приложениям), где и происходит загрузка Startup:


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

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

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

Startup должен включать метод Configure. В Configure определяется, какие сервисы будут использоваться в конвейере запроса. В следующем примере (взятом из стандартного шаблона web-сайта), несколько методов расширения используются для настройки конвейера с поддержкой:
• BrowserLink
• Error pages
• Static files
• ASP.NET Core MVC
• Identity


public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

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

В итоге мы имеем разделение среды выполнения и приложения, что дает нам возможность осуществить переход на другую платформу в будущем.
Заметка: Для более глубокого понимания ASP.NET Core Startup и Middleware, можно изучить Startup in ASP.NET Core


Хранение конфигураций


ASP.NET поддерживает сохранение настроек. Например, это настройки, которые используются средой выполнения, где было развернуто приложение. Сам подход заключался в том, что для хранение пользовательских key-value пар использовалась секция <appSettings> в файле Web.config:


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

Приложение получало доступ к этим настройкам с помощью коллекции ConfigurationManager.AppSettings из пространства имен System.Configuration :


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"
    }
  },
  // Здесь можно указать настраиваемые параметры конфигурации. Поскольку это JSON, все представлено в виде пар символов: значение  
 // Как назвать раздел, определяет сам разработчик
  "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"];

Есть другие способы, основанные на данном подходе, которые позволяют сделать процесс более надежным, например Dependency Injection (DI).
Подход DI обеспечивает доступ к строго типизированному набору объектов конфигурации.


// Предположим, AppConfiguration - это класс, который представляет строго типизированную версию раздела AppConfiguration
services.Configure<AppConfiguration>(Configuration.GetSection("AppConfiguration"));

Заметка: Для более глубокого понимания конфигураций ASP.NET Core, можно ознакомится с Configuration in ASP.NET Core.


Встроенный механизм Dependency Injection


Важной целью при создании больших масштабируемых приложений является ослабление связи между компонентами и сервисами. Dependency Injection – распространенная техника для решения данной проблемы и ее реализация является встроенным в ASP.NET Core компонентом.
В приложениях ASP.NET разработчики использовали сторонние библиотеки для внедрения Injection Dependency. Примером такой библиотеки является Unity .
Пример настройки Dependency Injection с 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);

    // Опустим остальную часть реализации
}

Далее производим инъекцию IProductRepository там, где это необходимо:


public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

}

Поскольку Dependency Injection является частью ядра ASP.NET Core, мы можем добавить свой сервис в метод ConfigureServices внутри Startup.cs:


public void ConfigureServices(IServiceCollection services)
{
    //Добавляем сервис приложения
    services.AddTransient<IProductRepository, ProductRepository>();
}

И далее инъекцию репозитория можно осуществить в любом месте, как и в случае с Unity.
Заметка: Подробности можно посмотреть в Dependency Injection in ASP.NET Core


Работа с статическими файлами


Важной частью веб-разработки является возможность обслуживания статики. Самые распространенные примеры статики — это HTML, CSS, JavaScript и картинки.
Эти файлы нужно сохранять в общей папке приложения (или например в CDN) чтобы в дальнейшем они были доступны по ссылке. В ASP.NET Core был изменен подход для работы с статикой.


В ASP.NET статика хранится в разных каталогах.
А в ASP.NET Core статические файлы по умолчанию хранятся в «web root» (/ wwwroot). И доступ к этим файлам осуществляется с помощью метода расширения UseStaticFiles из Startup.Configure:

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

К примеру, изображения находящееся в папке wwwroot/images будет доступно из браузера по адресу http://<app-address>/images/<imageFileName>.
Заметка: Если был выбран .NET Framework, то дополнительно нужно будет установить NuGet пакет Microsoft.AspNetCore.StaticFiles.
Заметка: Для более подробной ссылки на обслуживание статических файлов в ядре ASP.NET см. Introduction to working with static files in ASP.NET Core.


перевел poledustaren

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 17

    0

    Думаю единственное что меня разочаровало в 2.0 так это то что до сих пор не допилили DirectoryServices.


    Обещали в след релизе. Но я думаю затянут как всегда !

      0

      А меня в core разочаровал Entity Framework Core. Недавно столкнулся с тем что там GROUP BY очень ограничен, и не конвертируется нормально в SQL запрос.


      Обещают в 2.1 добавить.

        0
        А меня порадовало то что он раза так в 2-3 шустрее чем 6ой
          0
          Бенчмаркали или по ощущениям?
            0
            По ощущениям, да и по официальным бенчам
      0
      В .net core повсюду в примерах и книгах пишут код с async/await. Это прям дает мощный прирост скорости или просто модно/молодежно/хайп? (для маленьких и средних сайтов)
        0

        Core или нет, а неблокирующий вебсервер лучше блокирующего.

          0
          Можно и не писать, если у вас весь стек обработки запроса синхронный. А вот если асинхронный, то лучше потоку другие запросы пообрабатывать, чем, скажем, на IO у базы сидеть, в ожидании ответа.
            +1
            Не в .net core дело: асинхронный код пишут практически повсеместно начиная с .net 4.5, с момента появления TAP (до этого было мягко говоря неудобно). Писать асинхронный код в вебе — это хорошая практика. Особенно актуально, когда у вас в рамках обработки запроса имеются IO-операции или сложные cpu вычисления. Позволяет не блокировать поток в ожидании ответа от операции, а передать его в пул, чтобы он мог заниматься другими полезными вещами (например обрабатывать другие запросы). Наглядный пример асинхронной обработки: кассир в макдональдсе, он начинает принимать заказ у следующего посетителя, пока предыдущий посетитель ожидает, когда же его заказ будет сформирован и упакован.
            Async\await в свою очередь позволяет писать асинхронный код в виде синхронного кода, что облегчает чтение и понимание, ну и отладка гораздо проще.
              0
              Думаю тут все зависит. Await просто позволяет высвободить тред (пока задача пошла ждать чего-нибуть) для обработки другого запроса, так что теоретически, если нагрузка, т.е. число запросов небольшое, то и прироста никакого. С другой стороны, если у сервера вообще три треда в пуле, которые постоянно грузятся ожиданием ответа, скажем, медленного перфоратора что тот дырку закончил пробивать, то производительность, в смысле скорость отвера на другие запросы, вырастет. Думаю этот вопрос обсуждался триллион раз в инетах, но я не изучал.
                0
                Если вы про обработку запросов, то вроде и раньше рекомендовали использовать async/await.
                +1
                Entity Framework Core и все же деградирует товарищи
                  0
                  Перерыл весь интернет так не нашел событие на шутдаун. Не подскажите?
                    +1

                    А нельзя просто в Program.Main добавить код после вызова .Run()?


                    public static void Main(string[] args)
                    {
                        BuildWebHost(args).Run();
                        System.Console.WriteLine("Some code to execute on quit");
                    }
                      0
                      Скорее всего это не достижимый участок кода. Я так понял Run() занимает поток до конца жизни приложения. Но в конце жизни, потоки могут быть закрыты принудительно. Поэтому и нужно пользоваться предусмотренными функциями. А еще может быть эксепшн до вашего кода…
                        0

                        Я на простом проекте пробовал — текст печатался, так что код доступен. Про другие потоки не подумал, тут не подскажу к сожалению

                    +1

                    Насчёт "Явное указание файлов является необязательным для добавления их в проект": да, структура проекта стала проще и это хорошо. Плохо теперь, что в папке Migrations (и без того немаленькой) будет в два раза больше файлов.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое