Показаны сообщения с ярлыком ASP NET Core. Показать все сообщения
Показаны сообщения с ярлыком ASP NET Core. Показать все сообщения

вторник, 2 января 2018 г.

Развертывание Asp Net Core на IIS

Как развертывается Asp Net Core

Asp Net Core - это как Проктор энд Гэмбл т.е. два в одном флаконе: фрэймворк и Web-сервер, а само веб приложение  имеет вид консольного приложения, развертываемого через IWebHost с предварительной настройкой сервисов, миддлваре и т.п. Все относительно просто в случаях, когда разработка идет на машине с установленной Visual Studio. Для запуска веб приложений в Asp Net Core используется веб-сервер Kestrel. И, казалось, бы нет никаких сложностей для запуска веб-приложения на IIS. Но, столкнувшись с такой задачей я набил кучу шишек, о чем и хочу рассказать в этом посте.

Создание Net Core пула и подключение модуля AspNetCore

Как известно в IIS имеются пулы, которые, используются во-первых, для изоляции одних приложений от других, а, во-вторых, для простоты настройки однотипных приложений. И если запустить настройку пула приложения на IIS, то можно увидеть, что Net Core в нем напрочь отсутствует (предварительно я установил Net Core Sdk), всего 3 варианта таргет фрэймворка для пула: 2.0, 4.0 и неуправляемый код. Для Asp Net Core нужно выбрать последний. 

Но и это еще не все: выше я писал, что Asp Net Core веб-приложение развертывается на веб-сервере Kestrel, а как же тогда подключить IIS, дело в том, что IIS подключается как прокси: все запросы, пришедшие на HTTP порт приложения, развернутого на IIS, маршрутизируются через модуль AspNetCore в Kestrel, обрабатываются приложением, а ответ направляется в IIS. За это взаимодействие отвечает AspNetCore модуль, который необходимо установить в IIS отдельно от Net Core Sdk!!!! (https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/servers/aspnet-core-module). После инсталляции в списке модулей должна быть эта строка (предварительно (сначала) необходимо установить VS2015 C++ Redistributable Package):
Кроме всего этого в Web.config необходимо указать использование этого модуля и путь к целевой сборке (приложению), например, мое приложение - E3App.Web.dll и Web.Config имеет следующий вид:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore
        processPath="dotnet"
        arguments=".\E3App.Web.dll"
        stdoutLogEnabled="true"
        stdoutLogFile=".\logs\stdout" />
  </system.webServer>
</configuration>

Этот файл, как и многое другое, генерируется в процессе публикации проекта (Publish), однако, я не думал, что он играет роль, т.к. он в явном виде отсутствует в проекте. Но он очень важный! Особенно атрибут stdoutLogEnabled, по умолчанию этот атрибут имеет значение false, что для разработчика означает следующее: любое исключение произошедшее при запуске приложение будет "проглочено", а в качестве результата мы увидим на странице что-то вроде: "Оопс, а что-то сломалось". Включаем и идем дальше.

Старая добрая БД и EF Core

Следующим приколом на пути к запуску приложения стала сама БД: я поставил Sql Server 2012, предполагая, что EF нет дела до версии SQL сервера, но не тут-то было (хотя мне казалось, что ву меня был рабочий проект под 12 версию сервера). Текст исключения в стандартном выводе мне ничего не говорил о том, почему у меня ломается приложение при старте. Однако, подозрительным было то, что при отладке приложение стартовало без каких-либо проблем, а на IIS - ломалось. Тогда я решил заменить 12 SQL Server на SQL Server 2016 и вуаля, текст ошибки стал другим. Теперь приложение жаловалось на то, что нет прав на создании новой БД при подключении к таблице master (тут все тривиально, нужна роль sysadmin для пользователя под которым выполняется подключение). Хотелось бы сделать ремарку о том, что EF работает с БД по парадигме Code First (сам создает скрипт для создания БД, всех таблиц и связей между ними). Тут тоже пришлось помучиться, т.к. можно сделать строку подключения с полным указаниям логина и пароля (в appsettings.json):

  "ConnectionStrings": {
    "Storage": "Server=DEV-HOST\\SQLEXPRESS;Database=e3app;
    User ID=developer;Password=123;MultipleActiveResultSets=true"
  }


 Но это не всегда удобно использовать заранее созданную учетную запись, иногда необходимо использовать Windows-аутентификацию, казалось бы просто выдал права (роль sysadmin) своей Windows учетной записи:

"ConnectionStrings": {
    "Storage": "Server=DEV-HOST\\SQLEXPRESS;Database=e3app;
    Integrated Security=SSPI;MultipleActiveResultSets=true"
 }


 но нет в БД, оказывается, EF лезет через другую учетку. Экспериментальным способом включая данную роль у логинов по очереди, определил, что это  учетная запись BULTIN\Users (а если посмотреть, кто создал БД - IIS APPPOOL/AspNetCore).



Заключение

Казалось бы тривиальная задача, но она отняла у меня кучу времени, я убил, наверное, пару дней чистого времени пытаясь понять, что происходит и почему все работает не так, как нужно. Надеюсь, что этот пост поможет тем, кто столкнулся с такой же проблемой: деплоя на IIS Asp Net Core веб приложения.



четверг, 9 ноября 2017 г.

Десериализация в ASP NET Core 2.0

Будни кросс-платформенного Web от Microsoft

ASP NET Core базируется на платформе NET Core, которая, в свою очередь является кросс-платформенной, на данный момент доступна версия 2.0.х (у меня NET Core работает, что под Windows 7, что под Linux Mint 18).  Сам ASP NET Core включает в себя Entity Framework, Json Converter (от Newtonsoft), IoC и т.п. Сегодня пойдет речь о работе с Json, точнее о том, что я в итоге получил.

Приколы с POST и PUT и десериализацией из Json

Предположим у нас есть два проекта в солюшене, пусть они называются ClickPlatform.Dto и ClickPlatform.Mvc. В первом у нас объявлены классы Dto - объектов, а вот втором - Web API с набором контроллеров с методами (Get, Post, Put и Delete для обработки одноименных HTTP-методов и действий).

У нас есть следующий класс Dto:

    public class Listing
    {
          public Listing() { }

          public Listing(Guid id, string name, AdShortInfo[] linkedAd = null, string status = null,                                              TotalStatistics stats = null)
          {
               Id = id;
               Name = name;
               LinkedAd = linkedAd;
               Status = status;
               Stats = stats;
          }

          public Guid Id { get; set; }
          public string Name { get; set; }
          public AdShortInfo[] LinkedAd { get; set; }
          public string Status { get; set; }
          public TotalStatistics Stats { get; set; }
    }

Понятно, что для получения объекта и коллекции объектов используем Get-методы, с сериализацией тут все ок, все хорошо работает. и мы получаем либо коллекцию объектов, либо отдельный объект по id. Чудеса начинаются при использовании методов Post и Put внутри следующего контроллера

    [Produces("application/json")]
    [Route("api/[controller]")]
    public class ListingController : Controller
    {
        .// ...
       
        [HttpPost]
        public void Post([FromBody] Dto.Internal.Listing listing)
        {
    _listingManager.Create(listing);
        }

        [HttpPut("{id}")]
        public void Put([FromRoute]Guid id, [FromBody]Dto.Internal.Listing listing)
        {
            _listingManager.Update(id, listing);
        }
       
        // ...
   }

   Пробуем дернуть Post при этом устанавливаем content-type=application/json и charset=utf-8. и передаем следующий json: {"id":"","status":"active","name":"Evil_One","linkedAd":[], "stats":""}


Точка останова показывает нам, что наш объект - null.


Если мы поставим object, то получим следующее:

[HttpPost]
public void Post([FromBody] /*Dto.Internal.Listing*/ object listing)
{
     //_listingManager.Create(listing);
}


Теперь объект в теле определился, получается, что Json не может десериализовать объект в Dto.Internal.Listing. Почему же так происходит?  Все дело оказывается в том, что если для value type значение в Json не задано, то считается, что там null, а null нельзя использовать для value-типов, поэтому приходится везде ставить Nullable<T>. В вышеприведенном коде поменяет  свойство Guid Id {get; set;} на Guid? Id {get; set;}. И теперь смело можем передавать запрос со значением ""  в качестве Guid

При этом сам Newtonsoft.Json не соизволил проинформировать о том, что он не может десериализовать тип. Что ж вот такая она десериализация объектов в Asp NET.

Послесловие (послевкусие)
Попробовал я ASP NET Core и не могу сказать, что это супер платформа, не могу сказать, что это именно то, что нужно для разработки. В ней вроде как бы все есть, но что-то в ней не так. Многие вещи кажутся странными: как например молчаливая обработка ошибок, подтягивание Nuget пакетов для самых очевидных вещей вроде System.Data и многое другое. Если сюда добавить работу только на VS17 (на мой взгляд, худшая студия начиная с 2003, даже 2010 была лучше).





Распространение Windows-приложений (Chocolatey)

Менеджеры пакетов для ОС Windows В большинстве дистрибутивов Linux есть свои менеджеры пакетов: в Ubuntu/Mint это apt и deb, в OpenSuse э...