Руководство по установке и запуску сервера с простой базой данных

Фон

Недавно я решил приложить некоторые усилия, чтобы начать новый проект для сообщества, в котором я участвую в свое свободное время - конечной целью является некий устойчивый веб-портал, который можно использовать для облегчения сотрудничества между коллегами (вероятно, тема для некоторых будущий пост). Я решил решить, какие технологии я буду использовать для создания веб-сайта, и после некоторых размышлений решил опробовать .NET Core на Ubuntu с PostgreSQL в качестве базы данных.

Я выбрал .NET Core по нескольким причинам:

  1. На моей нынешней работе большую часть года я занимался разработкой почти исключительно на .NET и влюбился в эту платформу.
  2. Я хотел опробовать платформу, которую я уже знаю и люблю, но без привязки к системе Windows.
  3. Это совершенно новое - кому не нравится опробовать новейшие выпущенные крутые штуки?

Я мог бы попытаться придумать причины, по которым я выбрал PostgreSQL вместо MySQL или какой-либо другой альтернативы, но правда в том, что мой приятель убедил меня попробовать это некоторое время назад (определенно что-то большее в области «Чувак MySQL такой отстой, Postgres на тонну лучше! Почему бы вам никогда не использовать его ?! »), и я просто никогда не находил причины не использовать его, так как в целом это был довольно приятный опыт. По общему признанию, я не очень разбираюсь в базах данных и понимаю, что почти наверняка есть более эффективные способы делать то, что я делаю - это то, что я активно исследую, чтобы попытаться улучшить процесс, о котором я сейчас расскажу.

Эта проблема

Как я упоминал ранее, одной из главных причин выбора .NET Core было то, что он очень новый. Кто бы мог подумать, что использование технологии, которая вышла из бета-тестирования всего 2 месяца (на момент написания этой статьи), вызовет какие-либо головные боли? Большую часть времени я потратил на создание этого проекта, собирая вместе части моих разрозненных сообщений в блогах или вопросы о переполнении стека, пока у меня не было достаточно информации, чтобы собрать воедино что-то, что работало так, как я хотел. Таким образом, мотивация для этого поста состоит в том, чтобы задокументировать для себя, как я сделал то, что я сделал, а также для любой другой бедняги, которая пытается сделать что-то подобное.

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

Настройка сервера

Справедливое предупреждение: следующее руководство работает в основном при условии, что вы используете Ubuntu или что-то подобное. Я почти уверен, что большая часть информации, которую я собираюсь написать, применима не только к Unix-подобным системам, но я лично не проверял ее, чтобы знать наверняка.

Предпосылки

Прежде чем мы начнем, необходимо установить несколько вещей, а именно .NET Core и Postgres.

Хотя это и не обязательно, я также использую pgAdmin3 для управления своими базами данных Postgres. Вы можете на 100% управлять базой данных из командной строки, если хотите, но я лично предпочитаю иметь графический интерфейс, чтобы видеть, что происходит в моей базе данных. Я буду писать руководство так, как будто вы пользуетесь теми же инструментами, что и я, но не стесняйтесь заниматься своими делами, если знаете, что делаете.

Я также использую Visual Studio Code для разработки. Это отличная альтернатива Visual Studio при работе с ОС, отличной от Windows. Встроенная интеграция с git, языковые плагины и, самое главное, возможность отлаживать ваш код.

Я настоятельно рекомендую следовать инструкциям по установке Microsoft .NET Core из приведенной выше ссылки (в основном потому, что они могут измениться в будущем).

Инструкции по установке Postgres в основном должны остаться без изменений и выглядят следующим образом:

sudo apt-get update
sudo apt-get install postgresql postgresql-contrib
sudo apt-get install pgadmin3

Сборка сервера

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

mkdir test-project
cd test-project
mkdir Core
cd Core
dotnet new
dotnet restore
dotnet run

После того, как вы запустите все эти команды, у вас должен появиться какой-то вывод в консоли, указывающий, что все скомпилировано правильно, в идеале с причудливым маленьким «Hello World!» сообщение в конце.

Ваше рабочее пространство должно выглядеть примерно так. project.json управляет зависимостями нашего проекта, а Program.cs служит точкой входа в наш проект.

Прямо сейчас у нас есть только консольное приложение, поэтому обновите Program.cs следующим образом, чтобы настроить веб-сервер.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Core
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();
            host.Run();
        }
    }
}

Создайте новый файл с именем Startup.cs на том же уровне, что и Program.cs, с помощью следующего кода.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace Core
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {            
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    "Hello World. The Time is: " + 
                    DateTime.Now.ToString("hh:mm:ss tt"));
                                                               
            });
        }
    }
}

Затем добавьте зависимость сервера Kestrel в свой project.json.

"dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0"
  }

Убедитесь, что вы находитесь в новой папке проекта Core в своем терминале, и выполните следующие команды.

dotnet restore
dotnet run

Вам придется выполнять восстановление каждый раз, когда вы добавляете новые зависимости. Перейдите к localhost: 5000, и вы увидите одну из самых интересных веб-страниц в мире. Обновление страницы меняет время!

Теперь у нас есть работающий веб-сервер на .NET Core, хотя он почти ничего не делает. Следующим шагом будет добавление MVC в наш проект.

Добавление MVC

Первый шаг - обновить Startup.cs некоторым новым кодом, чтобы включить .NET MVC Framework на нашем сервере.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Core
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

А затем обновите также Program.cs, чтобы мы могли использовать виртуальные пути на сервере.

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Core
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();
            host.Run();
        }
    }
}

Затем добавьте новые зависимости в project.json.

"dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final"
  }

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

Вот как теперь выглядит обновленная файловая структура с этими новыми файлами. Небольшое примечание: мне не удалось свернуть папку .vscode перед тем, как сделать этот снимок экрана. Пока не обращайте на это внимания, потому что это не имеет отношения к тому, о чем я сейчас говорю. Он касается настройки отладки в Visual Studio Code, что является темой для отдельной публикации.

using System;
using Microsoft.AspNetCore.Mvc;
namespace Core.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet("/")]
        public ActionResult Index()
        {
            ViewBag.Message = "Hello!";
            ViewBag.Time = DateTime.Now;
            return View();
        }
    }
}

То же самое с Index.cshtml. Здесь тоже ничего безумного не происходит.

<label>Message:</label>
@ViewBag.Message
<hr/>
<label>Time:</label>
@ViewBag.Time

Обратите внимание, что мы просто используем Viewbag для отправки данных в наше представление. Вы также можете передать модель с данными, которые хотите отобразить (пока у нас нет никаких моделей, но мы доберемся до них).

Последнее изменение, которое нам нужно добавить, чтобы заставить эту работу работать, - это небольшое дополнение к project.json в разделах buildOptions и tools ( frameworks скорее всего между этими двумя разделами… просто оставьте как есть).

"buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
"frameworks":{...},
"tools": {
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
  }

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

Установите новые зависимости и запустите сервер. После перехода на страницу вы должны увидеть новое представление с данными, переданными из HomeController.

Напомним, где все файлы должны выглядеть на данный момент.

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Core
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();
            host.Run();
        }
    }
}

Startup.cs

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Core
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

HomeController.cs

using System;
using Microsoft.AspNetCore.Mvc;
namespace Core.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet("/")]
        public ActionResult Index()
        {
            ViewBag.Message = "Hello!";
            ViewBag.Time = DateTime.Now;
            return View();
        }
    }
}

Index.cshtml

<label>Message:</label>
@ViewBag.Message
<hr/>
<label>Time:</label>
@ViewBag.Time

project.json

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  },
  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
  }
}

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

Настройка базы данных

По большому счету, сообщения, которые я прочитал при настройке, основывались на Entity Framework для создания миграций. По общему признанию, первая миграция кода - это довольно круто - вы можете избежать SQL и быстро создать базу данных на основе уже написанных вами моделей C #. У меня две основные проблемы с этим:

  1. Вы передали контроль над созданием вашей базы данных стороннему инструменту.
  2. Вы должны потратить время на то, чтобы спланировать схему своей базы данных для ее фиксации; ваша база данных не должна быть чем-то нестабильным, как модель.

Я бы предпочел потратить дополнительное время на то, чтобы вручную написать SQL-код, который проектирует мою базу данных. При создании и изменении таблиц я предпочитаю точно знать, какой код запускается для их генерации. Одним из недостатков этого подхода является то, что обновление таблицы в вашей базе данных означает, что вам необходимо обновить соответствующую модель (и я полагаю, что все, что тесно связано с ней), хотя при достаточном планировании на уровне базы данных эти случаи должны быть минимальными.

Итак, приступим. Перед добавлением любого из скриптов, которые будут создавать и изменять базу данных, нам необходимо создать новую папку в корневом каталоге нашего проекта.

Новая структура папок выглядит так.

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

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

CREATE ROLE testuser LOGIN
  ENCRYPTED PASSWORD 'test'
  NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION;

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

CREATE DATABASE "TestDatabase"
  WITH OWNER = testuser
       ENCODING = 'SQL_ASCII'
       TABLESPACE = pg_default
       LC_COLLATE = 'C'
       LC_CTYPE = 'C'
       CONNECTION LIMIT = -1;

Измените имена пользователей / базы данных и пароль по своему усмотрению. Здесь важно то, что у бэкэнда для сервера есть собственный пользователь для доступа к базе данных, и что новый пользователь владеет новой базой данных.

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

  1. Новое окно запроса, подключенное к нашей новой TestDatabase, необходимо использовать для любых команд SQL, которые должны его вызвать.
  2. Любые сценарии, изменяющие TestDatabase, необходимо сохранить в нашем проекте, поскольку теперь они являются сценариями миграции. Я обычно отмечаю время своих сценариев миграции, чтобы порядок, в котором происходило с моей базой данных, был очевиден.

Выполните следующее, чтобы создать новую таблицу.

CREATE TABLE "user"(
Id serial PRIMARY KEY,
FirstName varchar(255) NOT NULL,
LastName varchar(255) NOT NULL,
Email varchar(255) NOT NULL,
Password varchar(255) NOT NULL
);
ALTER TABLE "user"
  OWNER TO testuser;
INSERT INTO "user"(FirstName, LastName, Email, Password) VALUES(
  'test', 'user', '[email protected]', 'test'
);

Я сохраняю этот файл в папке migrations с именем 08_31_2016_1500_Create-User-Table.sql - метка времени состоит из даты и времени (выраженного в 24-часовом формате) скрипта был написан, за которым следует краткое описание того, что делает сценарий.

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

Подключение сервера к базе данных

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

Нам нужно создать файл конфигурации, в котором подробно описано, как сервер должен подключаться к базе данных. Создайте appsettings.json на том же уровне, что и файлы Program.cs и Startup.cs, и присвойте ему следующее.

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "DbContextSettings" :{
    "ConnectionString" : "User ID=testuser;Password=test;Host=localhost;Port=5432;Database=TestDatabase;Pooling=true;"
  }
}

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

На следующем этапе нам понадобится DatabaseContext, поэтому сейчас мы создадим его. Это мало что даст, но позволит нам запустить наш сервер без ошибок, прежде чем мы перейдем к следующему разделу. Создайте новую папку с именем Models в папке Core, затем папку Database в этой папке Models, затем создайте DatabaseContext.cs со следующим.

using Microsoft.EntityFrameworkCore;

namespace Core.Models.Database
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
    }
}

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

Затем нам нужно обновить Startup.cs, чтобы использовать appsettings.json.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Core.Models.Database;
namespace Core
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }
        public IConfigurationRoot Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            var connectionString =  Configuration["DbContextSettings:ConnectionString"];
            services.AddDbContext<DatabaseContext>(opts =>  opts.UseNpgsql(connectionString));
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

Последний шаг - добавить новые зависимости в project.json. Обновленные разделы должны выглядеть так.

{
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
    "Microsoft.EntityFrameworkCore": "1.0.0",
    "Microsoft.EntityFrameworkCore.Design": "1.0.0-preview2-final",
    "Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.0"
  },
  "frameworks": {...},
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.0.0-preview2-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
  }
}

Установите зависимости и запустите проект, чтобы убедиться, что ничего не происходит катастрофически. Вы должны просто получить тот же результат, что и до того, как мы настроили базу данных.

Получение информации из базы данных

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

Создайте User.cs в том же каталоге, что и DatabaseContext.cs.

using System.ComponentModel.DataAnnotations.Schema;
namespace Core.Models.Database
{
    [Table("user")]
    public class User
    {
        [Column("id")]
        public int Id { get; set; }
        [Column("firstname")]
        public string FirstName { get; set; }
        [Column("lastname")]
        public string LastName { get; set; }
        [Column("email")]
        public string Email { get; set; }
        [Column("password")]
        public string Password { get; set; }
    }
}

Здесь мы указали, с какой таблицей связана эта модель, а также с каким столбцом связано каждое поле.

Нам нужно обновить DatabaseContext.cs, чтобы он знал о таблице пользователей через нашу новую модель.

using Microsoft.EntityFrameworkCore;

namespace Core.Models.Database
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }

        public DbSet<User> Users{ get; set; }
    }
}

Теперь, когда у нас есть DatabaseContext, который знает, как получить доступ к нашей таблице пользователей, нам нужно создать репозиторий для хранения всех функций, которые работают с этой таблицей. Создайте новую папку репозиториев в разделе Core, а затем добавьте UserRepository.cs со следующим.

using System.Linq;
using Core.Models.Database;

namespace Core.Respositories
{
    public class UserRepository
    {
        private readonly DatabaseContext _databaseContext;

        public UserRepository(DatabaseContext databaseContext)
        {
            _databaseContext = databaseContext;
        }

        public User GetByEmail(string email)
        {
            return _databaseContext.Users.SingleOrDefault(x => x.Email == email);
        }

        public void Save(User user)
        {
            var userFromDb = _databaseContext.Users.SingleOrDefault(x => x.Id == user.Id);
            if(userFromDb != null)
            {
                userFromDb = user;
            }
            else
            {
                _databaseContext.Users.Add(user);
            }
            _databaseContext.SaveChanges();
        }
    }
}

Этот репозиторий имеет некоторые базовые функции, которые позволяют нам получать и сохранять информацию о пользователях через внедренный DatabaseContext. Метод save является более или менее повторной реализацией метода AddOrUpdate () предыдущей версии Entity Framework - похоже, что Entity Framework Core в настоящее время не хватает этой функции, но, вероятно, в ближайшем будущем она будет реинтегрирована.

Чтобы наш UserRepository мог быть использован для нас, мы должны создать его новый экземпляр в HomeController, а затем использовать его для получения некоторых данных из базы данных. Это работает путем внедрения DatabaseContext и его использования для инициализации репозитория, как показано ниже.

using System;
using Microsoft.AspNetCore.Mvc;
using Core.Models.Database;
using Core.Respositories;
namespace Core.Controllers
{
    public class HomeController : Controller
    {
        private readonly UserRepository _userRepository;
        public HomeController(DatabaseContext databaseContext)
        {
            _userRepository = new UserRepository(databaseContext);
        }
        [HttpGet("/")]
        public ActionResult Index()
        {
            var email = "[email protected]";
            var user = _userRepository.GetByEmail(email);
            
            return View(user);
        }
    }
}

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

@model Core.Models.Database.User
<div>@Model.FirstName @Model.LastName</div>
<div>@Model.Email</div>

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

Я не рекомендую использовать вашу модель базы данных для заполнения представлений любого типа - у вас должна быть выделенная модель представления, которая используется для отображения данных (или возврата данных в любое место, которое будет доступно пользователю, например, с результатом JSON). Для этого мы внесем некоторые изменения.

Самое простое, но, возможно, самое утомительное изменение - сначала переименовать текущий файл User.cs в UserDatabaseModel.cs и обновить имя класса, чтобы оно соответствовало. Убедитесь, что вы обновили везде, где ранее в проекте упоминалась модель User.

Далее мы хотим создать модель представления. Создайте папку View в каталоге Models и добавьте UserViewModel.cs.

namespace Core.Models.View
{
    public class UserViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
}

В настоящее время у нас есть модель базы данных и модель представления, но нет возможности перейти от первого к второму. Итак, давайте сделаем один - создайте папку Mappers в Core и добавьте UserViewModelMapper.cs.

using Core.Models.Database;
using Core.Models.View;

namespace Core.Mappers
{
    public static class UserViewModelMapper
    {
        public static UserViewModel MapFrom(UserDatabaseModel user)
        {
            return new UserViewModel
            {
                FirstName = user.FirstName,
                LastName = user.LastName,
                Email = user.Email
            };
        }
    }
}

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

Теперь мы хотим использовать этот новый сопоставитель в HomeController для передачи UserViewModel в представление, а не UserDatabaseModel. Для этого мы воспользуемся нашим репозиторием пользователей, чтобы получить информацию о пользователе, а затем сопоставить эту модель базы данных с моделью представления.

using System;
using Microsoft.AspNetCore.Mvc;
using Core.Models.Database;
using Core.Respositories;
using Core.Mappers;
namespace Core.Controllers
{
    public class HomeController : Controller
    {
        private readonly UserRepository _userRepository;
        public HomeController(DatabaseContext databaseContext)
        {
            _userRepository = new UserRepository(databaseContext);
        }
        [HttpGet("/")]
        public ActionResult Index()
        {
            var email = "[email protected]";
            var user = _userRepository.GetByEmail(email);
            var userViewModel = UserViewModelMapper.MapFrom(user);
            return View(userViewModel);
        }
    }
}

Последний шаг - обновить Index.cshtml, чтобы использовать UserViewModel вместо UserDatabaseModel.

@model Core.Models.View.UserViewModel
<div>@Model.FirstName @Model.LastName</div>
<div>@Model.Email</div>

Замечание о моделях представлений: они должны быть плоскими, то есть они должны иметь как можно больше простых типов (string, int, double, bool и т. Д.). Очевидно, что это невозможно постоянно, и, безусловно, бывают случаи, когда более сложные типы имеют больше смысла, но, как правило, более плоские модели представления легче извлекать данные и сериализовать.

Теперь, когда этот проект завершен, давайте взглянем на окончательную файловую структуру проекта Core.

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

HomeController.cs

using System;
using Microsoft.AspNetCore.Mvc;
using Core.Models.Database;
using Core.Respositories;
using Core.Mappers;
namespace Core.Controllers
{
    public class HomeController : Controller
    {
        private readonly UserRepository _userRepository;
        public HomeController(DatabaseContext databaseContext)
        {
            _userRepository = new UserRepository(databaseContext);
        }
        [HttpGet("/")]
        public ActionResult Index()
        {
            var email = "[email protected]";
            var user = _userRepository.GetByEmail(email);
            var userViewModel = UserViewModelMapper.MapFrom(user);
            return View(userViewModel);
        }
    }
}

UserViewModelMapper.cs

using Core.Models.Database;
using Core.Models.View;
namespace Core.Mappers
{
    public static class UserViewModelMapper
    {
        public static UserViewModel MapFrom(UserDatabaseModel user)
        {
            return new UserViewModel
            {
                FirstName = user.FirstName,
                LastName = user.LastName,
                Email = user.Email
            };
        }
    }
}

DatabaseContext.cs

using Microsoft.EntityFrameworkCore;
namespace Core.Models.Database
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
        public DbSet<UserDatabaseModel> Users{ get; set; }
    }
}

UserDatabaseModel.cs

using System.ComponentModel.DataAnnotations.Schema;
namespace Core.Models.Database
{
    [Table("user")]
    public class UserDatabaseModel
    {
        [Column("id")]
        public int Id { get; set; }
        [Column("firstname")]
        public string FirstName { get; set; }
        [Column("lastname")]
        public string LastName { get; set; }
        [Column("email")]
        public string Email { get; set; }
        [Column("password")]
        public string Password { get; set; }
    }
}

UserViewModel.cs

namespace Core.Models.View
{
    public class UserViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
}

UserRepository.cs

using System.Linq;
using Core.Models.Database;
namespace Core.Respositories
{
    public class UserRepository
    {
        private readonly DatabaseContext _databaseContext;
        public UserRepository(DatabaseContext databaseContext)
        {
            _databaseContext = databaseContext;
        }
        public UserDatabaseModel GetByEmail(string email)
        {
            return _databaseContext.Users.SingleOrDefault(x => x.Email == email);
        }
        public void Save(UserDatabaseModel user)
        {
            var userFromDb = _databaseContext.Users.SingleOrDefault(x => x.Id == user.Id);
            if(userFromDb != null)
            {
                userFromDb = user;
            }
            else
            {
                _databaseContext.Users.Add(user);
            }
            _databaseContext.SaveChanges();
        }
    }
}

Index.cshtml

@model Core.Models.View.UserViewModel
<div>@Model.FirstName @Model.LastName</div>
<div>@Model.Email</div>

appsettings.json

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "DbContextSettings" :{
    "ConnectionString" : "User ID=testuser;Password=test;Host=localhost;Port=5432;Database=TestDatabase;Pooling=true;"
  }
}

Program.cs

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Core
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();
            host.Run();
        }
    }
}

project.json

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
    "Microsoft.EntityFrameworkCore": "1.0.0",
    "Microsoft.EntityFrameworkCore.Design": "1.0.0-preview2-final",
    "Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.0"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  },
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.0.0-preview2-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
  }
}

Startup.cs

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Core.Models.Database;
namespace Core
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }
        public IConfigurationRoot Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            var connectionString = Configuration["DbContextSettings:ConnectionString"];
            services.AddDbContext<DatabaseContext>(opts => opts.UseNpgsql(connectionString));
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

Заключение

И вот оно! Теперь у нас есть сервер .NET Core, работающий с использованием Entity Framework Core для извлечения данных из нашей базы данных PostgreSQL. Он почти самый простой, но его должно быть легко построить на основе того, что у нас есть.

Одна часть этого процесса, которая определенно требует работы, - это автоматизация выполнения сценариев базы данных, но, как я уже упоминал ранее, я активно занимаюсь этим, когда пишу это. Как только я выберу свое решение, я, скорее всего, опубликую его подробно.

Все исходники проекта, построенного в этом посте, можно найти на моем гитхабе. Я буду поддерживать и развивать этот проект в будущем, надеюсь дать мне повод сделать еще несколько (в идеале менее длинных) описаний.