Wednesday, February 17, 2016

Начало работы с ASP.NET Core

Здравствуйте, уважаемые читатели. Сегодня пост будет о том, как я писал свое первое приложение под ASP vNext. Наверное, этот пост будет больше о том, чтобы увековечить в блоге свой первый пример на ASP.NET vNext. Это как написание первого “Hello Worldна каком-то новом языке программирования. Мне в целом понравилось использовать vNext. Есть, правда, в нем вещи, которые невероятно раздражают, но об этом мы еще поговорим в процессе написания приложения. Суть в том, что сперва я хотел написать приложение, задача которого будет отображать список студентов, редактирование и удаление этого списка. До этого примера я сделал эти примеры на ASP.NET MVC 5 в двух вариантах: серверную часть (ApiController) на Web API и клиентскую – с помощью динамической генерации страницы – библиотека mustache.js (до этого я это все проделал на JsRender), и второе приложение также на серверной части было как Web API, а весь клиент был построен на AngularJS. Потому и возникла идея этот же пример для начала создать на ASP.NET vNext, только с MVC подходом. Что нужно для того, чтобы можно было работать с ASP.NET vNext:
  1. Установить для вашей студии Visual Studio 2015 Update 1.
  2. После установки обновления нужно установить ASP.NET 5.
  3. Затем установить доступность command-line инструментов для ASP.NET 5, выполнив в командной строке команду dnvm upgrade.
  4. Для тех, у кого стоит Windows 7 или Windows Server 2008 R2, нужно еще установить  Visual C++ Redistributable for Visual Studio 2012 Update 4.
Можно все делать по инструкции и сэкономить время себе. Но так как я не искал легких путей и сначала не посмотрел, что нужно ставить Visual Studio 2015 Update 1, то я убил где-то 2 дня, чтобы попытаться оживить 5-ую бета-версию, которая стояла раньше по умолчанию. 
Особенно "весело" в предыдущей бете было работать с пакетами. У меня они часто крешились, ругались на последовательность и кучу других самых разнообразных ошибок, вплоть до падения студии. Пункт 2 также можно пройти по-другому. Для этого после установки обновления на студию достаточно просто попробовать создать новый проект на ASP.NET 5 – и вы бы увидели окно, как показано на рисунке ниже.
У вас бы сразу скачался инсталлер, и можно было бы нормально работать. После успешной установки меню у вас не изменится.
Затем нам нужно по той же инструкции установить себе .NET Version Manager (DNVM). Для этого в командной строке нужно выполнить следующую команду:
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "&{$Branch='dev';iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Home/dev/dnvminstall.ps1'))}"
Как оказалось, у меня по какой-то причине не работал DNX (.NET Execution Environment), когда я пытался писать какие-то простые примеры на vNext. А всего-то нужно было прочитать инструкцию перед началом работы.
Затем нужно установить упомянутый выше DNX для .NET Core, так как именно через него мы будем создавать миграции для базы данных. Для этого в командной строке нужно выполнить следующую команду:
dnvm upgrade -r coreclr
Если нам нужно поставить DNX для всего .Net Framework, то нужно выполнить следующую команду:
dnvm upgrade -r clr
Если же у вас что-то пошло не так, то, вероятно, вам поможет инструкция Step-by-step installation instructions for getting DNX on your Windows machine. Как показала практика, если не выполнить начальную подготовку, дальше нечего и соваться. Если у вас все предыдущие этапы не вызвали особых проблем с установкой, предлагаю приступить к созданию нашего тестового приложения. Для начала создадим новый новое ASP.NET Web Application приложение и назовем его StudentVnextSample.
На следующем этапе выберем среди ASP.NET 5 Templates шаблон Web Application, для того чтобы у нас уже была построена какая-то начальная структура.
Затем нужно подождать, пока все пакеты отресторятся. К сожалению, это занимает прилично времени. Помните, в начале статьи упоминалось об одном раздражительном нюансе. В Visual Studio добавили новый пакетный менеджер, который называется Bower. Если откроете Dependencies, то сможете там увидеть два пакетных менеджера.
Если мы нажмем правой клавишей мишки на проекте, то мы сможем увидеть этот новый менеджер.
Вот как выглядит этот пакетный менеджер:
Соответственные изменения коснулись и самого NuGet Package Manager. Сейчас мы не можем загрузить все, как раньше, через NuGet, потому что пакеты стали несовместимы. Ниже на рисунке показано, почему они несовместимы и как можно их загрузить.
Пакетный менеджер Bower работает поверх json. Я имею ввиду, что язык, на котором вы сейчас пишете добавление пакетов,  это json. Список всех установленных файлов доступен в project.json.
Почему Microsoft отказалась от использования XML, как это было раньше для конфигурации приложения, не знаю. Больше всего это смахивает на то, что Майкрософт просто всех подстраивает под свою дудку.
Так вот, нормального управления этим всем добром сейчас нет. Из команд доступны только команды ниже, которые и решают ситуацию.
Спасает только то, что Intellisence работает нормально. Удалять пакет достаточно просто. Удалил строку в файле конфига  и вся информация обновляется.
Думаю, достаточно о Bower. Если вы обратили внимание, то там есть еще один пакетный менеджер – npm (Node Package Manager). Этот менеджер должен работать в поставке сразу, но у меня он не заработал. Поэтому пришлось ставить Node.js, в котором этот пакет идет как основная составляющая. Например, последний Angular 2 (на момент написания статьи была доступна бета-версия) можно поставить только через npm.
Например, как упомянуто выше, без npm вы Angular 2 бету не сможете запустить 5 MIN QUICKSTART. Хотя толку с того, если вы даже ее запустите, мало. Подогнать Angular 2, для того чтобы тестовый пример хотя бы заработал, удовольствие еще то, учитывая, что браузеры еще не поддерживают ES6. Но это совсем другая история.
Итак, на ваше усмотрение, есть три пакетных менеджера, использование которых нужно рассматривать в зависимости от ситуации.
Пока мы не начали добавление классов, я бы сразу хотел показать, в чем была суть DNX, который мы устанавливали. Если мы откроем вкладку Refereces, то сможем увидеть, что у нас используется в проекте.
Чуть позже мы рассмотрим, как использовать DNX на примере миграции БД.
Перейдем к практике и поработаем с Entity Framework 7. Для начала в папку Models добавим новый класс Student, как показано ниже.
public class Student
{
    [Key]
    public int Id { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
    [StringLength(50, MinimumLength = 1)]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "First Name")]
    [StringLength(50, MinimumLength = 2, ErrorMessage = "First name must be between 2 and 50 characters.")]
    public string FirstName { get; set; }

    [NotMapped]
    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}
Я использовал классическую DataAnnotations, поэтому ничего нового здесь вы не увидите, за исключением одной хитрости. Заметьте: поле FullName я поставил как NotMapped. Для Entity Framework 6 я не делал такого трюка с установкой NotMapped, и это поле не попадало в базу и не генерировало никаких ошибок. Но каково же было мое удивление, когда в первом своем проекте после миграции и запуска приложения я получил ошибку, что у меня в свойстве FullName нет сеттера (set). А потом и вовсе приложение начало уходить в down, как на картинке ниже с падением Visual Studio.
Кругом спасения стало прибить процесс вручную. Полный текст ошибки:
Но давайте не будем о печальном и добавим в DbContext наш класс Student, чтобы этот класс был привязан к контексту. Предлагаю, чтобы не усложнять себе жизнь созданием отдельного контекста, добавить реализацию в уже существующий контекст ApplicationDbContext. Выглядеть это будет так:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Student> Students { get; set; }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}
Вы, наверняка, хорошо помните с EF 6 такую возможность установки инициализатора для создания БД.
public class SchoolContext : DbContext
{
    public SchoolContext() : base("SchoolConnectionString")
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<SchoolContext, Migrations.Configuration>("SchoolConnectionString"));
    }
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new StudentEntityConfiguration());
    }
}
Через класс Database.SetInitializer. Так вот, хорошая новость заключается в том, что обо всех этих инициализаторах можно забыть.
Теперь мы либо работаем с существующий базой, генерируя себе сущности через scaffolding, либо миграции. Третьего не дано. Давайте перед началом добавления миграции добавим себе контроллер для работы с нашей сущностью Student. Добавлять новый контроллер через Scaffolding теперь также можно двумя способами.
Первый, как и раньше,  через меню Controller, второй новый способ – через меню New Scaffolded Item… Для разнообразия выберем второй новый вариант.
Заметьте, в отличие от MVC 5, у нас осталось лишь два варианта создания контроллера: либо MVC, либо Web API. А кроме того, сейчас нет разделения между этим двумя понятиями. В MVC 5 у нас было два контроллера: для MVC класс Controller, а для WebApi – соответственно, ApiController. Сейчас у нас остался лишь класс Controller, что можно увидеть, открыв, например, HomeController.
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    }

    public IActionResult Contact()
    {
        ViewData["Message"] = "Your contact page.";

        return View();
    }

    public IActionResult Error()
    {
        return View();
    }
}
Наша же задача – создать MVC новый контроллер для наших студентов, поэтому мы выбираем вариант, как на рисунке ниже.
Затем нужно выбрать модель, контекст и нажать на кнопку Add.
Если контроллер по какой-то причине не создался, просто пересоберите проект. Это должно помочь, если у вас в проекте нет ошибок. После этого мы увидим, что наш контроллер был успешно создан, а также были созданы представления для него.
Давайте добавим, как в тестовом примере, какие-то начальные тестовые данные. А добавляются они с помощью Dependency Injection, а если быть еще точнее, то в vNext появился свой простой IoC контейнер, что позволяет не быть завязанным на какие-то сторонние контейнеры, как Unity или Autofac, для примера. Для этого нам в наш файл project.json в раздел dependencies нужно добавить следующий пакет Microsoft.Extensions.DependencyInjection:
Затем нажимаем "Сохранить" и ждем, пока нужный пакет поставится. Этот пакет ставит не что иное как собственную реализацию паттерна Service Locator.
Теперь добавим наши тестовые данные. Для этого в папку Models добавим класс SampleData, как показано ниже.
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;

namespace StudentVnextSample.Models
{
    public class SampleData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            var context = serviceProvider.GetService<ApplicationDbContext>();
            context.Database.Migrate();
            if (!context.Students.Any())
            {
                context.Students.Add(
                    new Student { LastName = "Austen", FirstName = "Jane" });
                context.Students.Add(
                    new Student { LastName = "Dickens", FirstName = "Charles" });
                context.Students.Add(
                    new Student { LastName = "Cervantes", FirstName = "Miguel" });

                context.SaveChanges();
            }
        }
    }
}
Затем открываем файл Startup.cs и добавляем в конец метода Configure после строки
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});
следующую строку кода:
SampleData.Initialize(app.ApplicationServices);
Полный метод приведен ниже.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

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

        // For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859
        try
        {
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                .CreateScope())
            {
                serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                        .Database.Migrate();
            }
        }
        catch { }
    }

    app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());

    app.UseStaticFiles();

    app.UseIdentity();

    // To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715

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

    SampleData.Initialize(app.ApplicationServices);
}
Попробуем собрать наш проект, чтобы проверить, что он работает.
А теперь, пожалуй, расскажу, как же все-таки EF 7 знает, как нам использовать наш контекст. Здесь будет немного нужной теории, поэтому немного потерпите.
Во-первых, поскольку мы добавили наш класс Student в ApplicationDbContext, то подразумевается, что мы будем использовать миграцию. Почему я так решил? Просто посмотрел внимательно на метод, который привел выше.
// For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859
try
{
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
        .CreateScope())
    {
        serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                .Database.Migrate();
    }
}
catch { }
Не понимаю специалистов, которые пытаются просто глотать ошибки, как здесь. Если у нас не выполнилась миграция, то возможно, мы что-то сделали не так, не правда ли? Но код выше, который я привел для примера, – это уже сама миграция. А в ответе на вопрос, откуда ApplicationDbContext знает, к какой базе подключается, приходит на помощью IoC контейнер, который стал частью ASP.NET vNext. Не слышал, чтобы ему давали какое-то кодовое имя, его всюду называют простым IoC контейнером. Поэтому мы также не будем отклоняться от традиции. Ниже приведен метод, на который и возложена задача установить все для нашего контекста.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}
IServiceCollection – это и есть тот IoC контейнер, о котором я писал выше.
Название у него немного странное; к нему нужно привыкнуть. А в целом, с простыми задачами он справляется на ура.
Первое, на что стоит обратить внимание в этом методе,  так это то, что сейчас базу данных, которую мы будем использовать, мы генерируем через extension property типа AddEntityFramework() и добавление самой конфигурации. Раньше мы это делали на уровне конфиг файла, сейчас же делаем с помощью fluent синтаксиса, или точечной нотации, – как кому удобно. Подключение к базе хранится сейчас в файле appsettings.json.
{
  "Data": {
    "DefaultConnection": {
      "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-StudentVnextSample-61e27d16-0312-48ee-ba14-4452422cfc46;Trusted_Connection=True;MultipleActiveResultSets=true"
    }
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}
Как вы понимаете, использовать подход как раньше – все через конфиг файл не выйдет, так что множество методов, которые позволяют сделать это процесс более интуитивно понятным, – только на руку C# разработчикам. Я никогда особо не любил ковыряется в файле конфигурации, поэтому новый подход даже как-то греет душу.
Следующим шагом идет добавление Identity.
services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();
А затем мы говорим нашему DI фреймворку зарегистрировать MVC фреймворк сервисы в DI системе.
services.AddMvc();
Так как MVC и WebAPI объединили в ASP.NET 5, то у нас сейчас всего лишь один котроллер, и здесь больший интерес вызывает то, как же происходит регистрация routing. А все достаточно просто. В следующем методе Configure есть вызов метода UseMvc, на плечи которого и ложится сейчас данная задача.
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});
Наверное, у многих из вас возникнет резонный вопрос: а как же тогда отделять Web API метод от MVC метода. Здесь тоже все просто: все решают атрибуты. Пример приведен ниже.
[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET: api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
        return "value";
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody]string value)
    {
    }

    // PUT api/values/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/values/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }
}
Мне кажется, не хуже, чем это было раньше. Возможно, даже лучше. Тем более, для любителей копаться во внутренностях ASP.NET 5 будет интересно, что же изменилось в жизненном цикле приложения.
Ну и последние строчки в нашем методе:
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
Это всего-на-всего пустышка для того чтобы вы понимали, как добавлять свои сервисы.
Вроде, все рассмотрели по подключению к базе данных, а также как работает DI, пора переходить к миграциям. Потому что с новым EF7 есть только два способа работы: готовая база и создание контекста и доменной модели через scaffolding, либо путь номер два, который мы выбрали, – через миграции.
У нас за миграции отвечает DNX (раньше мы делали все через Console Package Manager). Список команд, которые он поддерживает, приведены ниже. Возможно, вам они пригодятся.
А нам же нужно их использовать. Для этого откройте командную строку и укажите путь в директории, в которой вы создали ваш проект.
У меня это диск D, поэтому ваш путь может отличаться.
Затем выполните следующий список команд:
dnu restore
dnx ef migrations add Initial
dnx ef database update
Кстати, по этим командам есть заметка, что они означают и как работает сейчас help по EF7.
После проделанных действий у вас будет создана новая миграция и применена к вашей базе данных.
Полный код сгенерированной миграции приведен ниже.
public partial class Initial : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropForeignKey(name: "FK_IdentityRoleClaim<string>_IdentityRole_RoleId", table: "AspNetRoleClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserClaim<string>_ApplicationUser_UserId", table: "AspNetUserClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserLogin<string>_ApplicationUser_UserId", table: "AspNetUserLogins");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<string>_IdentityRole_RoleId", table: "AspNetUserRoles");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<string>_ApplicationUser_UserId", table: "AspNetUserRoles");
        migrationBuilder.CreateTable(
            name: "Student",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                FirstName = table.Column<string>(nullable: true),
                LastName = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Student", x => x.Id);
            });
        migrationBuilder.AlterColumn<string>(
            name: "UserId",
            table: "AspNetUserLogins",
            nullable: false);
        migrationBuilder.AlterColumn<string>(
            name: "UserId",
            table: "AspNetUserClaims",
            nullable: false);
        migrationBuilder.AlterColumn<string>(
            name: "RoleId",
            table: "AspNetRoleClaims",
            nullable: false);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityRoleClaim<string>_IdentityRole_RoleId",
            table: "AspNetRoleClaims",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserClaim<string>_ApplicationUser_UserId",
            table: "AspNetUserClaims",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserLogin<string>_ApplicationUser_UserId",
            table: "AspNetUserLogins",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<string>_IdentityRole_RoleId",
            table: "AspNetUserRoles",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<string>_ApplicationUser_UserId",
            table: "AspNetUserRoles",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropForeignKey(name: "FK_IdentityRoleClaim<string>_IdentityRole_RoleId", table: "AspNetRoleClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserClaim<string>_ApplicationUser_UserId", table: "AspNetUserClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserLogin<string>_ApplicationUser_UserId", table: "AspNetUserLogins");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<string>_IdentityRole_RoleId", table: "AspNetUserRoles");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<string>_ApplicationUser_UserId", table: "AspNetUserRoles");
        migrationBuilder.DropTable("Student");
        migrationBuilder.AlterColumn<string>(
            name: "UserId",
            table: "AspNetUserLogins",
            nullable: true);
        migrationBuilder.AlterColumn<string>(
            name: "UserId",
            table: "AspNetUserClaims",
            nullable: true);
        migrationBuilder.AlterColumn<string>(
            name: "RoleId",
            table: "AspNetRoleClaims",
            nullable: true);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityRoleClaim<string>_IdentityRole_RoleId",
            table: "AspNetRoleClaims",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserClaim<string>_ApplicationUser_UserId",
            table: "AspNetUserClaims",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserLogin<string>_ApplicationUser_UserId",
            table: "AspNetUserLogins",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<string>_IdentityRole_RoleId",
            table: "AspNetUserRoles",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<string>_ApplicationUser_UserId",
            table: "AspNetUserRoles",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
    }
}
Эта миграция немного отличается от той, что была у вас в предыдущей версии EF6. Как выглядит наша табличка после миграции, показано ниже.
Если же вы не знаете, как к ней подключиться через Visual Studio, то здесь вы сможете найти ответ How to connect to LocalDB in Visual Studio Server Explorer?
Если не указать NotMapped для FullName в классе Student, то у вас сгенерируется следующая табличка:
Теперь нам осталось добавить навигацию на нашу новую страницу и на наш котроллер. Для этого открываем папку Views/Share и открываем файл _Layout.cshtml. В нем находим строки
<li><a asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-controller="Home" asp-action="Contact">Contact</a></li>
И после них добавляем следующую строку.
<li><a asp-controller="Students" asp-action="Index">Students</a></li>
Надеюсь, в приведенном выше коде вы заметили изменения? Тогда обратите внимание на тот же код в ASP.NET MVC 5.
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
Теперь вы точно заметили разницу. Helper методы канули в лету, и вместо них пришли Tag Helpers. В примере выше мы видим только часть из них: например, asp-controller и asp-action. А так их очень и очень много. А теперь заброшу ложку дегтя в бочку мёда. Как вы думаете, у них обстоят дела с поддержкой Intellisence? Правильно, как вы, наверное, догадались, все очень печально.
Никакой нормальной подсветки синтаксиса, хотя я не совсем прав. Она срабатывает раз через раз.
Мне больше понравилось еще одна фишка – использование PartialAsync с ключевым словом await, что вы можете увидеть также на картинке выше.
Хватит вас уже томить описанием новых возможностей vNext. Давайте запустим наше приложение, чтобы убедиться в том, что оно у нас все-таки работает. 
Вот такое симпатичное окно появится у вас после запуска:
Давай попробуем создать нового студента. Например, по имени John Carter.
Проверим также, что работает поле Details.
И удаление элементов:

Итоги
Мне кажется, у нас с вами получилось добиться того, что мы и планировали. Осталось теперь подвести краткие итоги статьи. В целом, новый ASP.NET vNext мне очень понравился. Немного смущает, что очень долго происходит запуск проекта. Плюс много лишних действий нужно делать для того, чтобы проделать примитивную операцию. Хотя есть и много плюсов. Одна из них – это принцип "все свое ношу с собой". Вы можете на лету конфигурировать под нужную платформу. Да, ASP.NET vNext теперь кросплатформенный. В целом, Microsoft двигается в очень правильном направлении. 
Если у вас появились какие-то вопросы в процессе прочтения, буду рад на них ответить. А в ближайшее время будет продолжение погружение в vNext, но на этот раз – со стороны Web API.
Исходники к статье: ASP.NET vNext First Sample

No comments:

Post a Comment