[Из песочницы] Краткое руководство: связываем ASP.NET Core Web API + Angular 5

История о том, как подружить два отдельных проекта ASP.NET Core Web API и Angular 5, и заставить их работать, как одно целое.

Вступление


Данная статья рассчитана на новичков, которые делают первые шаги в изучении Angular в связке с .NET Core.

Если вы используете Visual Studio для разработки, то наверное уже встречались с готовыми шаблонами проектов с подключенным Angular. Данные шаблоны позволяют в пару кликов создать приложение, которое уже имеет настроенный роутер и несколько готовых компонент. Вам не нужно тратить время на минимальную настройку рабочего приложения: вы уже имеете рабочий WebPack, отдельный модуль для общих компонент, настроенный роутер, подключенный Bootstrap. Возможно, вы подумаете: «Супер! Круто! Половина дела сделана!». Но на самом деле все немного сложнее…

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

  1. Жесткая связь веб-интерфейса с серверной часть
  2. Сильно усложненная минимально рабочая версия приложения
  3. Отсутствие возможности использовать Angular CLI
  4. Лишние предустановленные пакеты
  5. Нарушение некоторых принципов из Angular Style Guide

Различные best-practice советуют нам разделять приложение на два отдельных проекта, что в нашем случае — .NET Core Web API и Angular проекты. Основными преимуществами такого подхода будет следующее:

  1. Два независимых друг от друга проекта, что позволит нам в дальнейшем реализовать альтернативный интерфейс, не трогая проект с серверной частью
  2. Суженный глобальный search scope, что позволяет эффективнее и проще производить поиск
  3. Абстрагированность от рабочего окружения, в котором разрабатывается серверная часть, Visual Studio например — мы можем использовать VS Code, Sublime Text, Atom или другой удобный для вас редактор

При таком раскладе конечных сценария продакшена два:

  1. Вы хостите веб-интерфейс на одном адресе, а сервер — на другом
  2. Либо собираете магическим образом проекты в один и хостите только его

Моей задачей являлся как раз второй сценарий, так был он был более предпочтительным по экономическим соображениям. И вот, когда я пытался разобраться с тем, каким же все-таки образом подружить .NET Core Web API проект с Angular проектом, так, чтобы во время разработки у нас было два отдельных проекта, а в продакшене — всего один, а конкретно .NET Core веб-сайт, то я так и не смог найти полноценного руководства «с нуля до рабочего приложения». Пришлось собирать по кусочкам решения с англоговорящих форумов и блогов. Если у вас вдруг появилась такая же задача, то достаточно будет прочитать мою статью.

Поехали!


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

  • .NET Core SDK — 2.0 версии или выше
  • Node.js — 8.9.0 версии или выше
  • npm — 5.5.0 версии или выше
  • Angular CLI — 1.6.5 версии или выше
  • Visual Studio Code

Если у вас уже установлена Visual Studio 2017 и при установке вы выбирали .NET Core Development, то .NET Core SDK у вас уже есть и устанавливать его не нужно. Однако Node.js отдельно придется установить даже если был выбран Node.js Development. Npm установится вместе с Node.js. Angular CLI устанавливается глобально из командной строки через npm (инструкция есть по ссылке выше).

Теперь нам следует проверить, все ли установлено и готово к работе. Для этого откройте командную строку (терминал) и выполните подряд команды, перечисленные ниже:

dotnet --version #Версия .NET Core
node --version   #Версия Node.js
npm --version    #Версия npm
ng --version     #Версия Angular CLI


Результат командной строки (версии могут отличаться, ничего страшного)
tv08tkswfvpuaq8lucrnfrdtvvg.png


Создаем проект .NET Core Web API


В данной статье я буду выполнять все действия через командную строку и VS Code, так как он поддерживает .NET Core. Однако, если для вас предпочтительна Visual Studio 2017 для работы с .NET проектами, то можете смело создать и редактировать проект через нее.

Шаг первый


Создаем корневую папку проекта Project, открываем ее в VS Code, запускаем терминал сочетанием клавиш Ctrl + ~ (тильда, буква ё). Пока ничего сложного:)

Окно VS Code и запущенный терминал
qdmlj4yx-t9gda40t6spyqkr2jc.png


Шаг второй


Теперь нам нужно создать проект. Для этого выполняем команду:

dotnet new webapi -n Project.WebApi


Окно VS Code с открытым проектом и запущенный терминал
biz-x3umll2usgmzgacb2vdomgi.png


Шаг третий


Проверяем все ли работает. Через терминал переходим в папку с только что созданным проектом, после выполняем команду:

dotnet run


Окно VS Code с открытым проектом и запущенный терминал
nwkiq9a6tmdq6lj4utn-dcvcgsk.png


Шаг четвертый


Если на прошлом шаге все прошло успешно и в консоль было выведено Now listening on: localhost:5000, значит сервер успешно запущен. Перейдем по адресу localhost:5000/api/values (тестовый контроллер, который создается автоматически). Вы должны увидеть JSON с тестовыми данными.

Результат в браузере
qmkkz8azg_rkxq1aurlg8c4l4l0.png


Шаг пятый


Возвращаемся в VS Code и в терминале нажимаем Ctrl + C, чтобы остановить работу сервера.

Окно VS Code с открытым проектом и запущенный терминал
z5k7acbkbtcrrqadry3f6x2wvqg.png


Создаем проект Angular


Теперь создадим проект Angular. Для этого будем использовать команды Angular CLI, VS Code и встроенный терминал.

Шаг первый


В терминале переходим в корневую папку нашего проекта Project и создаем новый проект с названием Project.Angular (придется немного подождать):

cd ..\
ng new Project.Angular


Окно VS Code с открытым проектом и запущенный терминал
mmlagexgebeu5iwrqcnffefoamq.png


Шаг второй


Перейдем в терминале в папку только что созданного проекта и запустим его:

cd ./Project.Angular
ng serve --open


Окно VS Code с открытым проектом и запущенный терминал
1emdjzzckmb56djmovkxp9exsre.png


Шаг третий


Если на прошлом шаге все прошло успешно и в консоль было выведено NG Live Development Server is listening on localhost:4200, значит сервер успешно запущен. Перейдем по адресу localhost:4200. Вы должны увидеть тестовую страничку Angular.

Результат в браузере
whzk93njkmqhmte0omb19hxdoo8.png


Шаг четвертый


Возвращаемся в VS Code и в терминале нажимаем Ctrl + C, вводим Y, чтобы остановить работу сервера.

Окно VS Code с открытым проектом и запущенный терминал
7muey9srurmdyc9md-pkhhanvda.png


Настраиваем проект Angular


Теперь нам нужно настроить две вещи: proxy.config.json для перенаправления запросов к серверу на нужный порт, и самое главное — настройка сборки в папку wwwroot.

Шаг первый


Создаем в корне проекта Project.Angular файл с названием proxy.config.json и добавляем в него следующее содержимое:

{
    "/api/*": {
        "target": "http://localhost:5000/",
        "secure": false,
        "logLevel": "debug"
    }
}


proxy.config.json
{
    "/api/*": {
        "target": "http://localhost:5000/",
        "secure": false,
        "logLevel": "debug"
    }
}


Данная настройка указывает на то, что все запросы начинающиеся с /api/… будут попадать на localhost:5000/. То есть результирующим запросом будет localhost:5000/api/…

Шаг второй


Укажен Angular, что в режиме разработки нам нужно использовать этот proxy.config. Для этого открываем файл package.json (который находится там же, в корне), находим команду scripts → start и заменяем значение на:

{
    ...
    scripts: {
        ...
        "start": "ng serve --proxy-config proxy.config.json",
    }
}    


package.json
{
    {
        "name": "project.angular",
        "version": "0.0.0",
        "license": "MIT",
        "scripts": {
            "ng": "ng",
            "start": "ng serve --proxy-config proxy.config.json",
            "build": "ng build --prod",
            "test": "ng test",
            "lint": "ng lint",
            "e2e": "ng e2e"
        },
        "private": true,
        "dependencies": {
            "@angular/animations": "^5.2.0",
            "@angular/common": "^5.2.0",
            "@angular/compiler": "^5.2.0",
            "@angular/core": "^5.2.0",
            "@angular/forms": "^5.2.0",
            "@angular/http": "^5.2.0",
            "@angular/platform-browser": "^5.2.0",
            "@angular/platform-browser-dynamic": "^5.2.0",
            "@angular/router": "^5.2.0",
            "core-js": "^2.4.1",
            "rxjs": "^5.5.6",
            "zone.js": "^0.8.19"
        },
        "devDependencies": {
            "@angular/cli": "1.6.7",
            "@angular/compiler-cli": "^5.2.0",
            "@angular/language-service": "^5.2.0",
            "@types/jasmine": "~2.8.3",
            "@types/jasminewd2": "~2.0.2",
            "@types/node": "~6.0.60",
            "codelyzer": "^4.0.1",
            "jasmine-core": "~2.8.0",
            "jasmine-spec-reporter": "~4.2.1",
            "karma": "~2.0.0",
            "karma-chrome-launcher": "~2.2.0",
            "karma-coverage-istanbul-reporter": "^1.2.1",
            "karma-jasmine": "~1.1.0",
            "karma-jasmine-html-reporter": "^0.2.2",
            "protractor": "~5.1.2",
            "ts-node": "~4.1.0",
            "tslint": "~5.9.1",
            "typescript": "~2.5.3"
        }
    }      
}    


В дальнейшем для запуска проекта Angular будем использовать команду npm start вместе ng serve. Команда npm start является сокращением для команды, которая указана у вас в package.json.

Шаг третий


Последним шагом будет простая настройка сборки (по команде) проекта в папку wwwroot .NET Core Web API проекта. В открытом файле package.json находим команду scripts → build и заменяем значение на следующее:

{
    ...
    scripts: {
        ...
        "build": "ng build --prod --output-path ../Project.WebApi/wwwroot",
    }
}    


package.json
{
    {
        "name": "project.angular",
        "version": "0.0.0",
        "license": "MIT",
        "scripts": {
            "ng": "ng",
            "start": "ng serve --proxy-config proxy.config.json",
            "build": "ng build --prod --output-path ../Project.WebApi/wwwroot",
            "test": "ng test",
            "lint": "ng lint",
            "e2e": "ng e2e"
        },
        "private": true,
        "dependencies": {
            "@angular/animations": "^5.2.0",
            "@angular/common": "^5.2.0",
            "@angular/compiler": "^5.2.0",
            "@angular/core": "^5.2.0",
            "@angular/forms": "^5.2.0",
            "@angular/http": "^5.2.0",
            "@angular/platform-browser": "^5.2.0",
            "@angular/platform-browser-dynamic": "^5.2.0",
            "@angular/router": "^5.2.0",
            "core-js": "^2.4.1",
            "rxjs": "^5.5.6",
            "zone.js": "^0.8.19"
        },
        "devDependencies": {
            "@angular/cli": "1.6.7",
            "@angular/compiler-cli": "^5.2.0",
            "@angular/language-service": "^5.2.0",
            "@types/jasmine": "~2.8.3",
            "@types/jasminewd2": "~2.0.2",
            "@types/node": "~6.0.60",
            "codelyzer": "^4.0.1",
            "jasmine-core": "~2.8.0",
            "jasmine-spec-reporter": "~4.2.1",
            "karma": "~2.0.0",
            "karma-chrome-launcher": "~2.2.0",
            "karma-coverage-istanbul-reporter": "^1.2.1",
            "karma-jasmine": "~1.1.0",
            "karma-jasmine-html-reporter": "^0.2.2",
            "protractor": "~5.1.2",
            "ts-node": "~4.1.0",
            "tslint": "~5.9.1",
            "typescript": "~2.5.3"
        }
    }      
}    


Для выполнения этого действия выполните в терминале команду npm run build. Результатом будет собранные файлы проекта в папке wwwroot.

Настраиваем проект .NET Core Web API


Осталось научить серверную работать со статическими файлами и разрешать запросы с другого порта.

Шаг первый


Открываем Startup.cs и добавляем в метод Configure строчки, позволяющие серверу обрабатывать статические файлы:

app.UseDefaultFiles();
app.UseStaticFiles();


Метод Configure в Startup.cs
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseDefaultFiles();
        app.UseStaticFiles();

        app.UseMvc();
    }
    


Шаг второй


В Startup.cs, в метод Configure, добавляем строку, позволяющую серверу принимать запросы с порта 4200:

app.UseCors(builder => builder.WithOrigins("http://localhost:4200"));


Метод Configure в Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseCors(builder => builder.WithOrigins("http://localhost:4200"));

    app.UseMvc();
}


Шаг третий


В методе ConfigureServices добавляем поддерку CORS:

services.AddCors();


Метод ConfigureServices в Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();

    services.AddMvc();
}


В конечном итоге файл Startup.cs должен иметь содержимое, которое представлено ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Project.WebApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddCors(); // <-- Добавили это
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseDefaultFiles(); // <-- Это
            app.UseStaticFiles(); // <-- Вот это

            app.UseCors(builder => builder.WithOrigins("http://localhost:4200")); // <-- И вот так:)

            app.UseMvc();
        }
    }
}    


Готово! Теперь вы можете смело обращаться к вашим API контроллерам из Angular проекта. Также, вызвав команду npm run build для Angular проекта, у вас будет версия Web API приложения готовая для деплоя.

Заключение


Это было краткое руководство по тому, что нужно сделать, чтобы иметь два отдельных проекта, и заставить их работать как одно целое.

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

В данной статье я не осветил несколько моментов, связанных с маршрутизацией в Web API проекте, более гибкой настройкой CORS, автоматической сборкой и т.д. В планах написать более подробную статью, как собрать уже продакшен версию такого варианта приложения. Если вдруг у вас возникнут вопросы, то пишите в комментарии или на любой из контактов, указанных в профиле, я постараюсь вам помочь.

© Habrahabr.ru