.NET в Linux: проще, чем кажется
Опубликовано: 2022-08-16Разработка решений .NET для Linux всегда была сложной задачей, поскольку для работы Microsoft Visual Studio требуется Windows. Поработав над несколькими проектами .NET, я решил проверить пределы разработки .NET в Linux. В этом простом руководстве основное внимание уделяется приложению ASP.NET MVC с SQL Server, чтобы показать, насколько элегантной и эффективной может быть разработка .NET в предпочитаемой мной ОС.
Среда разработки
Во-первых, мы должны убедиться, что инструменты .NET и SDK, связанные с нашей конкретной разновидностью Linux, установлены с использованием стандартного руководства Microsoft.
Моя предпочтительная среда разработки состоит из оконной интегрированной среды разработки (IDE), мощного инструмента управления базой данных и запросов, самой базы данных и инструментов для построения и развертывания. Я использую следующие инструменты для достижения надежной функциональности и обеспечения красивого опыта кодирования:
- IDE: код Visual Studio
- Инструмент для управления базами данных и создания запросов: DBeaver
- База данных: Microsoft SQL Server (установка Linux)
- Инструменты сборки: интерфейс командной строки .NET SDK (CLI)
- Виртуальная машина и контейнеры: Docker
Убедитесь, что эти инструменты правильно установлены, прежде чем продолжить работу с нашим примером приложения.
Леса проекта
В этом примере приложения мы рассмотрим разработку и функциональность ASP.NET с помощью ряда вариантов использования гипотетической системы управления запасами обувного магазина. Как и в случае любого нового приложения .NET, нам потребуется создать решение, а затем добавить в него проект. Мы можем использовать инструменты командной строки .NET SDK CLI для построения нашего нового решения:
mkdir Shoestore && cd Shoestore dotnet new sln
Затем для простоты создайте проект ASP.NET, содержащий явный основной класс, поскольку эта структура проекта наиболее знакома разработчикам ASP.NET. Давайте создадим наш проект, используя паттерн MVC:
mkdir Shoestore.mvc && cd Shoestore.mvc dotnet new mvc --use-program-main=true
Затем добавьте проект в решение:
# Go to the root of the solution cd .. dotnet sln add Shoestore.mvc/
Теперь у нас есть решение по умолчанию и содержащийся в нем проект ASP.NET. Прежде чем продолжить, убедитесь, что все построено:
cd Shoestore.mvc/ dotnet restore dotnet build
Передовая практика разработки поощряет размещение ключевых служб и среды выполнения приложений в контейнерах Docker для улучшения развертывания и переносимости. Поэтому давайте создадим простой контейнер Docker для поддержки нашего приложения.
Переносимость приложений
Образы Docker обычно ссылаются на другой родительский образ Docker как на принятую отправную точку для основных требований, таких как ОС и базовые решения, включая базы данных. Следуя этому передовому опыту Docker, создайте файл Dockerfile и файл Docker Compose для правильной настройки службы, ссылаясь на родительские образы, опубликованные Microsoft. Мы будем использовать этапы Docker, чтобы наш образ оставался маленьким. Этапы позволяют нам использовать пакет SDK для .NET при создании нашего приложения, поэтому среда выполнения ASP.NET требуется только во время работы нашего приложения.
Создайте Shoestore.mvc
Shoesstore.mvc со следующим содержимым:
# Shoestore\Shoestore.mvc\Dockerfile # Build stage FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /shoestore COPY Shoestore.mvc/*.csproj ./ # Restore project packages RUN dotnet restore COPY Shoestore.mvc/* ./ # Create a release build RUN dotnet build -c Release -o /app/build # Run the application and make it available on port 80 FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app EXPOSE 80 # Assets and views COPY Shoestore.mvc/Views ./Views COPY Shoestore.mvc/wwwroot ./wwwroot COPY --from=build /app/build ./ ENTRYPOINT [ "dotnet", "Shoestore.mvc.dll" ]
Далее мы создадим файл docker-compose.yml
в корневом каталоге нашего решения. Изначально он будет содержать только ссылку на .Dockerfile
нашей службы приложений:
# Shoestore/docker-compose.yml version: "3.9" services: web: build: context: . dockerfile: Shoestore.mvc/Dockerfile ports: - "8080:80"
Давайте также настроим нашу среду с файлом .dockerignore, чтобы убедиться, что в наш образ копируются только артефакты сборки.
Теперь, когда наша служба приложения заглушена и ее среда выполнения готова к запуску, нам нужно создать службу базы данных и подключить ее к нашей конфигурации Docker.
Служба базы данных
Добавить Microsoft SQL Server в нашу конфигурацию Docker несложно, тем более что мы используем предоставленный Microsoft образ Docker без его изменения. Добавьте следующий блок конфигурации в конец файла docker-compose.yml
для настройки базы данных:
db: image: "mcr.microsoft.com/mssql/server" environment: SA_PASSWORD: "custom_password_123" ACCEPT_EULA: "Y" ports: - "1433:1433"
Здесь ACCEPT_EULA
предотвращает остановку установки, а наша настройка ports
позволяет использовать порт SQL Server по умолчанию без преобразования. При этом наш файл Compose включает в себя как нашу службу приложений, так и базу данных.
Прежде чем настраивать код приложения, давайте проверим, что наша среда Docker работает:
# From the root of the solution docker compose up --build
Предполагая, что во время запуска не возникает никаких ошибок, наш незавершенный образец приложения должен быть доступен через веб-браузер по локальному адресу http://localhost:8080
.
Инструменты генерации кода
Теперь мы можем сосредоточиться на самой интересной части: настройке кода приложения и обеспечении того, чтобы данные приложения сохранялись в базе данных Microsoft SQL Server. Мы будем использовать средства Entity Framework (EF) и .NET SDK, чтобы подключить приложение к базе данных и создать шаблон модели приложения, представления, контроллера и конфигурации, необходимой для EF.
Прежде чем мы сможем указать нужные нам инструменты, мы должны создать файл tool-manifest
:
# From the root of the solution dotnet new tool-manifest
Добавьте инструменты EF и SDK в этот файл с помощью этих простых команд:
dotnet tool install dotnet-ef dotnet tool install dotnet-aspnet-codegenerator
Чтобы проверить правильность установки этих инструментов, запустите dotnet ef
. Если единорог появляется, они установлены правильно. Затем запустите dotnet aspnet-codegenerator
, чтобы протестировать инструменты ASP.NET; вывод должен быть общим блоком использования CLI.
Теперь мы можем использовать эти инструменты для создания нашего приложения.
МВК: модель
Первой задачей при создании нашего приложения является создание модели. Поскольку эта модель будет добавлена в базу позже, мы включим в наш проект пакеты MS SQL Server и EF:
cd Shoestore.mvc/ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet restore
Затем создайте объект контекста базы данных EF, который определяет, какие модели добавляются в базу данных, и позволяет нашему коду легко получать доступ и запрашивать эти данные из базы данных.
Создайте каталог Data
для размещения кода, специфичного для EF, и создайте файл Data/ApplicationDBContext.cs
со следующим содержимым:
// Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs using Microsoft.EntityFrameworkCore; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options){} }
Затем настройте строку подключения к базе данных, которая должна соответствовать учетным данным, которые мы настроили в нашем Dockerfile
. Установите для содержимого Shoestore/Shoestore.mvc/appsettings.json
следующее:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Shoestore": "Server=db;Database=master;User=sa;Password=custom_password_123;" } }
С настроенной строкой подключения к базе данных и закодированным контекстом базы данных мы готовы закодировать функцию Main
нашего приложения. Мы добавим обработку исключений базы данных, чтобы упростить отладку системы. Кроме того, поскольку ошибка .NET в сгенерированном коде приводит к тому, что контейнер Docker неправильно обслуживает наши представления, нам потребуется добавить специальный код в конфигурацию нашей службы представлений. Это явно установит пути к файлам для нашего местоположения представления в нашем образе Docker:
using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Data; namespace Shoestore.mvc; // ... public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Associate our EF database context and configure it with our connection string var connectionString = builder.Configuration.GetConnectionString("Shoestore"); builder.Services.AddDbContext<ApplicationDBContext>( options => options.UseSqlServer(connectionString)); // Middleware to catch unhandled exceptions and display a stack trace builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // Add services to the container. // ASP.NET has a known issue where the final built app doesn't know where the view // files are (in the Docker container). // The fix is to specifically add view locations. builder.Services .AddControllersWithViews() .AddRazorOptions(options => { options.ViewLocationFormats.Add("/{1}/{0}.cshtml"); options.ViewLocationFormats.Add("/Shared/{0}.cshtml"); });
Перейдите к оператору IsDevelopment
if
в том же файле, чтобы добавить конечную точку миграции базы данных в нашу систему, когда она находится в режиме разработки. Добавьте оператор else
со следующим кодом:
// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { // Leave the contents of the if block alone. These are hidden for clarity. } else { app.UseMigrationsEndPoint(); }
Затем запустите быстрый тест, чтобы убедиться, что новые пакеты и изменения исходного кода компилируются правильно:
// Go to mvc directory cd Shoestore.mvc dotnet restore dotnet build
Теперь давайте заполним модель необходимыми полями, создав Shoestore.mvc\Models\Shoe.cs
:
namespace Shoestore.mvc.Models; public class Shoe { public int ID { get; set; } public string? Name { get; set; } public int? Price { get; set; } public DateTime CreatedDate { get; set; } }
EF генерирует SQL на основе связанной модели, ее файла контекста и любого кода EF в нашем приложении. Затем результаты SQL переводятся и при необходимости возвращаются в наш код. Если мы добавим нашу модель Shoe
в контекст нашей базы данных, EF будет знать, как выполнять перевод между MS SQL Server и нашим приложением. Давайте сделаем это в файле контекста базы данных, Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs
:
using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Models; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options) { } private DbSet<Shoe>? _shoe { get; set; } public DbSet<Shoe> Shoe { set => _shoe = value; get => _shoe ?? throw new InvalidOperationException("Uninitialized property" + nameof(Shoe)); } }
Наконец, мы будем использовать файл миграции базы данных, чтобы поместить нашу модель в базу данных. Инструмент EF создает файл миграции, характерный для MS SQL Server, на основе контекста базы данных и связанной с ней модели (т. е. Shoe
):
cd Shoestore.mvc/ dotnet ef migrations add InitialCreate
Давайте отложим запуск нашей миграции, пока у нас не будет контроллера и представления.
MVC: контроллер и представление
Мы создадим наш контроллер с помощью инструмента генерации кода ASP.NET. Этот инструмент очень мощный, но требует специальных вспомогательных классов. Используйте пакеты стилей Design
для базовой структуры контроллера и его интеграции с EF. Добавим эти пакеты:
cd Shoestore.mvc\ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design && \ dotnet add package Microsoft.EntityFrameworkCore.Design && \ dotnet restore
Теперь создать наш контроллер по умолчанию так же просто, как вызвать следующую команду:
cd Shoestore.mvc\ dotnet dotnet-aspnet-codegenerator controller \ -name ShoesController \ -m Shoe \ -dc ApplicationDBContext \ --relativeFolderPath Controllers \ --useDefaultLayout \ --referenceScriptLibraries
Когда генератор кода создает наш контроллер, он также создает простое представление для этого контроллера. Когда наши основы MVC завершены, мы готовы запустить все.
Миграция и тестирование приложений
Миграция EF обычно является простым делом, но когда задействован Docker, процесс усложняется. В следующей статье из нашей серии мы рассмотрим удивительно извилистый путь к тому, чтобы заставить эти миграции работать в нашем решении Docker, но сейчас мы просто хотим, чтобы наша миграция выполнялась.
Все файлы конфигурации и миграции включены в наш репозиторий. Давайте клонируем полный проект на нашу локальную машину и выполним миграцию:
git clone https://github.com/theZetrax/dot-net-on-linux.git cd ./dot-net-on-linux docker composer up
Операция docker composer
создает наше приложение, запускает миграцию и запускает наше приложение ASP.NET со средой выполнения .NET. Чтобы получить доступ к работающему решению, посетите http://localhost:8080/Shoes
.
Хотя интерфейс нашего приложения прост, он демонстрирует функциональность на всех уровнях, от представления до базы данных.
.NET в Linux просто работает
См. полный репозиторий для обзора нашего решения. В следующей статье мы подробно расскажем о нашей миграции, а также дадим советы и рекомендации, как сделать наши образы Docker компактными.
.NET в Linux — это больше, чем несбыточная мечта: это жизнеспособная комбинация языка, среды выполнения и ОС. Многие разработчики, выросшие в Visual Studio, могут не иметь опыта использования .NET CLI в полной мере, но эти инструменты эффективны и мощны.
Дальнейшее чтение в блоге Toptal Engineering:
- Создание веб-API ASP.NET с помощью ASP.NET Core
- 8 причин, по которым Microsoft Stack по-прежнему является жизнеспособным выбором
- Как повысить производительность приложений ASP.NET в веб-ферме с помощью кэширования
- Учебное пособие по Elasticsearch для разработчиков .NET
- Что такое Кубернетес? Руководство по контейнеризации и развертыванию
Блог Toptal Engineering выражает благодарность Хеноку Цегайе за рассмотрение примеров кода, представленных в этой статье.