Эта статья является частью серии о .NET Core и о том, как теперь он стал таким же простым и эффективным, как и любой другой стек, такой как Node или Rails, для создания современных приложений.

.NET Core - это круто, и вы должны это знать

.NET Core - это привлекательно - приложение командной строки

.NET Core - это круто - создание веб-API

Я намерен создать приложение для курирования, которое мы будем улучшать шаг за шагом, и в итоге мы получим платформу, полностью готовую к производству. Для начала мы начнем создавать простое приложение командной строки с базой данных Sqlite.

Наше целевое приложение

На этом этапе мы создаем приложение командной строки, которое будем использовать для хранения элементов, определяемых уникальным идентификатором, именем и датой создания. После этого мы сможем отобразить наш текущий список курирования и удалить из него элемент.

curator add medium.com/@jbuisson
curator add blog.cleancoder.com
curator list
> 1: medium.com/@jbuisson, 8/3/19 9:58:56 AM.
> 2: blog.cleancoder.com, 8/3/19 9:59:17 AM.
curator remove 1
curator list
> 2: blog.cleancoder.com, 8/3/19 9:59:17 AM.

Настроить приложение и пакеты

Создайте новое консольное приложение:

dotnet new console -o Curator

Мы будем использовать Microsoft.EntityFramework в качестве объектно-реляционного сопоставления (ORM) нашей базы данных, настроенного для Sqlite. Кроме того, мы будем использовать CommandLineUtils от Нейта Макмастера - отличный пакет для простого написания приложений командной строки.

Изнутри нашего недавно созданного проекта выполните следующие команды, чтобы добавить пакеты:

dotnet add package McMaster.Extensions.CommandLineUtils 
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design

Время писать код

Теперь мы должны создать нашу модель Item, чтобы сохранить ее в базе данных Sqlite.

У нас есть класс C # с общедоступными свойствами для получения / установки Id, Name и CreatedAt. Мы также переопределяем метод ToString () для отображения информации об элементе.

Затем мы создаем EntityFramework DbContext и настраиваем его для использования Sqlite.

Опять же, здесь нет ничего хитрого; DbContext служит мостом для доступа к данным из базы данных. Мы объявляем DbSet для модели Item, что указывает на то, что у нас есть Items в базе данных.

Кроме того, мы сообщаем EntityFramework UseSqlite путем указания пути к файлу базы данных Sqlite. Этот путь относительно двоичного каталога выполнения. Не стесняйтесь использовать любой другой путь, который считаете подходящим, но убедитесь, что целевой каталог существует; в противном случае вы можете столкнуться со следующей ошибкой приложения:

SQLite Error 14: ‘unable to open database file’.

Перед настройкой самого приложения мы создаем новую миграцию EntityFramework и запускаем ее для настройки базы данных.

dotnet ef migrations add InitialCreate
dotnet ef database update

Вы должны увидеть файл curator.db в каталоге, который вы ранее настроили в DbContext. Если вы не меняли путь источника данных, вы сможете найти его в корне рабочей области.

Наконец, мы создаем наши команды.

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

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

Последний шаг - обновить основную точку входа в наше приложение. Это должно привести к примерно следующему:

Пришло время проверить это.

dotnet run add medium.com/@jbuisson
dotnet run add blog.cleancoder.com
dotnet run list
> 1: medium.com/@jbuisson, 8/3/19 11:13:37 AM.
> 2: blog.cleancoder.com, 8/3/19 11:13:42 AM.
dotnet run remove 1
dotnet run list
> 2: blog.cleancoder.com, 8/3/19 11:13:42 AM.

Время рефакторинга

У нас есть работающее приложение из командной строки, но оно плохо спроектировано и не соблюдает принцип чистого кода.

Я стремлюсь к этой архитектуре проекта, разделив консольное приложение на несколько библиотек классов:

├── data
├── src
    ├── Curator.Console
    ├── Curator.Data
        ├── Curator.Data.Entities
        ├── Curator.Data.EntityFramework
            ├── Curator.Data.EntityFramework.Context
            ├── Curator.Data.EntityFramework.Sqlite
├── tests

Это может показаться несколько сложным - то есть иметь такую ​​глубину и разбивать Entities, Context и Sqlite на три разные библиотеки. Однако использование этого метода поддерживается по нескольким причинам:

  • Я должен иметь возможность использовать Entities без EntityFramework; это две проблемы со своими собственными обязанностями, и так далее следует разделять. Это не догматическое видение, а прагматическое. В какой-то момент мы могли бы удалить EntityFramework, чтобы использовать другой ORM - или вообще не использовать - и это не будет слишком дорогостоящим благодаря этому подходу.
  • EntityFramework позволяет нам использовать широкий спектр баз данных. Это означает, что при использовании одного и того же контекста мы могли использовать разные базы данных. Спойлер: мы сделаем это как при тестировании, так и при портировании приложения в веб-стек.

Папка данных просто служит нашим хранилищем Sqlite. Папка tests говорит сама за себя и будет использоваться после завершения рефакторинга.

Я также включу несколько проверок времени выполнения на предмет наличия null или недопустимых аргументов.

Писать тесты никогда не поздно

Я не могу осознанно рассматривать этот шаг без добавления некоторых тестов. Еще раз вы увидите, что легко настроить тесты на основе вашего кода, а затем запускать их.

Чтобы проверить нашу команду, мы не хотим использовать настоящую базу данных Sqlite. EntityFramework имеет поставщика InMemory, чтобы помочь нам в этом.

cd src/Curator.Data/Curator.Data.EntityFramework
dotnet new classlib -o Curator.Data.EntityFramework.Memory
cd Curator.Data.EntityFramework.Memory
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add reference ../Curator.Data.EntityFramework/Curator.Data.EntityFramework.Context/

Теперь нам нужно только создать новую DesignTimeDbContextFactory и настроить ее с помощью UseInMemoryDatabase. Уловка здесь состоит в том, чтобы добавить уникальное имя базы данных в каждый новый контекст DbContext, чтобы изолировать каждый тест. Для этого я использую DateTime.Now.Ticks, который достаточно безопасен, но вместо этого вы можете использовать Guid.NewGuid () или любой уникальный генератор случайных чисел.

Как только это будет завершено, мы должны перейти в папку тестов и создать новый тестовый проект.

cd tests
dotnet new xunit -o Curator.Console.Tests

Мы должны добавить ссылки на проект, который мы хотим протестировать, в дополнение к новому провайдеру базы данных, который мы только что создали:

dotnet add reference ../../src/Curator.Console
dotnet add reference ..\..\src\Curator.Data\Curator.Data.EntityFramework\Curator.Data.EntityFramework.Memory\Curator.Data.EntityFramework.Memory.csproj

Теперь вы можете написать простой тест для AddCommand:

Наконец, когда все тесты написаны, у нас есть чистое приложение командной строки, использующее .NET Core, совместимое с Linux, Windows и Mac! Из моей предыдущей истории вы теперь сможете легко выпустить это приложение:

dotnet publish src/Curator.Console -c Release — self-contained true -r [YOUR_RUNTIME_IDENTIFIER] -o [INSTALLATION_PATH]


Это наш первый шаг. Следующим будет веб-API, развернутый на сервере или с помощью Docker.