Вступление
Данная статья рассчитана на новичков, которые делают первые шаги в изучении Angular в связке с .NET Core.
Если вы используете Visual Studio для разработки, то наверное уже встречались с готовыми шаблонами проектов с подключенным Angular. Данные шаблоны позволяют в пару кликов создать приложение, которое уже имеет настроенный роутер и несколько готовых компонент. Вам не нужно тратить время на минимальную настройку рабочего приложения: вы уже имеете рабочий WebPack, отдельный модуль для общих компонент, настроенный роутер, подключенный Bootstrap. Возможно, вы подумаете: «Супер! Круто! Половина дела сделана!». Но на самом деле все немного сложнее…
Сложность и подводные камни заключаются в том, что у такого подхода и в стандартных шаблонах есть несколько существенных недостатков:
Различные best-practice советуют нам разделять приложение на два отдельных проекта, что в нашем случае — .NET Core Web API и Angular проекты. Основными преимуществами такого подхода будет следующее:
- Жесткая связь веб-интерфейса с серверной часть
- Сильно усложненная минимально рабочая версия приложения
- Отсутствие возможности использовать Angular CLI
- Лишние предустановленные пакеты
- Нарушение некоторых принципов из Angular Style Guide
При таком раскладе конечных сценария продакшена два:
- Два независимых друг от друга проекта, что позволит нам в дальнейшем реализовать альтернативный интерфейс, не трогая проект с серверной частью
- Суженный глобальный search scope, что позволяет эффективнее и проще производить поиск
- Абстрагированность от рабочего окружения, в котором разрабатывается серверная часть, Visual Studio например — мы можем использовать VS Code, Sublime Text, Atom или другой удобный для вас редактор
Моей задачей являлся как раз второй сценарий, так был он был более предпочтительным по экономическим соображениям. И вот, когда я пытался разобраться с тем, каким же все-таки образом подружить .NET Core Web API проект с Angular проектом, так, чтобы во время разработки у нас было два отдельных проекта, а в продакшене — всего один, а конкретно .NET Core веб-сайт, то я так и не смог найти полноценного руководства «с нуля до рабочего приложения». Пришлось собирать по кусочкам решения с англоговорящих форумов и блогов. Если у вас вдруг появилась такая же задача, то достаточно будет прочитать мою статью.
- Вы хостите веб-интерфейс на одном адресе, а сервер — на другом
- Либо собираете магическим образом проекты в один и хостите только его
Поехали!
Итак, что мы будем использовать? Нам потребуются следующие вещи:
Если у вас уже установлена Visual Studio 2017 и при установке вы выбирали .NET Core Development, то .NET Core SDK у вас уже есть и устанавливать его не нужно. Однако Node.js отдельно придется установить даже если был выбран Node.js Development. Npm установится вместе с Node.js. Angular CLI устанавливается глобально из командной строки через npm (инструкция есть по ссылке выше).
- .NET Core SDK — 2.0 версии или выше
- Node.js — 8.9.0 версии или выше
- npm — 5.5.0 версии или выше
- Angular CLI — 1.6.5 версии или выше
- Visual Studio Code
Теперь нам следует проверить, все ли установлено и готово к работе. Для этого откройте командную строку (терминал) и выполните подряд команды, перечисленные ниже:
dotnet --version #Версия .NET Core
node --version #Версия Node.js
npm --version #Версия npm
ng --version #Версия Angular CLI
Создаем проект .NET Core Web API
В данной статье я буду выполнять все действия через командную строку и VS Code, так как он поддерживает .NET Core. Однако, если для вас предпочтительна Visual Studio 2017 для работы с .NET проектами, то можете смело создать и редактировать проект через нее.
Шаг первый
Создаем корневую папку проекта Project, открываем ее в VS Code, запускаем терминал сочетанием клавиш Ctrl + ~ (тильда, буква ё). Пока ничего сложного:)
Шаг второй
Теперь нам нужно создать проект. Для этого выполняем команду:
dotnet new webapi -n Project.WebApi
Шаг третий
Проверяем все ли работает. Через терминал переходим в папку с только что созданным проектом, после выполняем команду:
dotnet run
Шаг четвертый
Если на прошлом шаге все прошло успешно и в консоль было выведено Now listening on: localhost:5000, значит сервер успешно запущен. Перейдем по адресу localhost:5000/api/values (тестовый контроллер, который создается автоматически). Вы должны увидеть JSON с тестовыми данными.
Шаг пятый
Возвращаемся в VS Code и в терминале нажимаем Ctrl + C, чтобы остановить работу сервера.
Создаем проект Angular
Теперь создадим проект Angular. Для этого будем использовать команды Angular CLI, VS Code и встроенный терминал.
Шаг первый
В терминале переходим в корневую папку нашего проекта Project и создаем новый проект с названием Project.Angular (придется немного подождать):
cd ..ng new Project.Angular
Шаг второй
Перейдем в терминале в папку только что созданного проекта и запустим его:
cd ./Project.Angular
ng serve --open
Шаг третий
Если на прошлом шаге все прошло успешно и в консоль было выведено NG Live Development Server is listening on localhost:4200, значит сервер успешно запущен. Перейдем по адресу localhost:4200. Вы должны увидеть тестовую страничку Angular.
Шаг четвертый
Возвращаемся в VS Code и в терминале нажимаем Ctrl + C, вводим Y, чтобы остановить работу сервера.
Настраиваем проект Angular
Теперь нам нужно настроить две вещи: proxy.config.json для перенаправления запросов к серверу на нужный порт, и самое главное — настройка сборки в папку wwwroot.
Шаг первый
Создаем в корне проекта Project.Angular файл с названием proxy.config.json и добавляем в него следующее содержимое:
{
"/api/*": {
"target": "http://localhost:5000/",
"secure": false,
"logLevel": "debug"
}
}
{
"/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",
}
}
{
{
"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",
}
}
{
{
"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();
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"));
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();
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, автоматической сборкой и т.д. В планах написать более подробную статью, как собрать уже продакшен версию такого варианта приложения. Если вдруг у вас возникнут вопросы, то пишите в комментарии или на любой из контактов, указанных в профиле, я постараюсь вам помочь.
Комментарии (18)
denismaster
20.02.2018 15:53Было бы интересно также прочитать про миграцию приложений, построенных «старым способом», на новый, описанный в статье или, как выше указали, в шаблонах)
Интересуют подводные камни и стоит ли вообще игра свеч)sergeyZ
20.02.2018 20:20В сущности, никакой миграции не нужно. Всего 3 шага:
1. Устанавливаем Microsoft.AspNetCore.SpaServices и Microsoft.AspNetCore.SpaServices.Extensions
2. Правим конфигурацию в Startup.cs
3. Создаём и настраиваем .angular-cli.json
Всё описано в документации.
Если до этого использовали webpack с хитрой конфигурацией, могут возникнуть трудности из-за того, что angular-cli на даёт такой гибкости. Да, можно извлечь weback.config из angular-cli, но это строго не рекомендуется разработчиками angular.
В моём проекте обновление того стоило. Содержать webpack.config.dev.js и webpack.config.prod.js больше не нужно — это экономит кучу времени.
Правда есть недостатки, с помощью angular-cli невозможно (пока) убрать локали moment.js из бандла. А это лишние 300 Кб.
mokeev1995
20.02.2018 17:56А теперь стоит вспомнить про SEO и Angular Universal и снова сменить архитектуру приложения. Либо на Node.Js -> Angular, либо интегрировать в Asp.Net Core. Таким образом ни одно из представленных решений не сможет нам помочь :)
IliaTrifonov Автор
20.02.2018 18:11Все знают, что ситуация с SEO для SPA приложений не очень хорошая. Но особого смысла делать SPA для обычного лендинга или сайта компании/продукта/итд нет. Single Page подход используются преимущественно именно для веб-приложений с широкой функциональностью. На мой взгляд, до тех пор пока Google не научится нормально индексировать одностраничные приложения, лучше сделать обычный лендинг по вашему продукту, а само приложение размещать отдельно, например на поддомене.
Что касается Server-Side Rendering. С такой архитектурой не прокатит, верно. Но если вы хотите использовать его, чтобы увеличить производительность, то можно попробовать поэкспериментировать с Lazy Loading и настроить .NET Core под это дело. Признаюсь, я не пробовал. Но кейс интересный — проверю.mokeev1995
20.02.2018 18:17SSR как раз таки главным образом и используют для того, чтоб можно было сгенерировать полноценную отрендеренную страницу и скормить её ботам поисковиков, но не для ускорения, его то как раз таким образом не достичь, ведь интерфейс быстрее не проинициализируется.
Для ускорения как раз подойдёт, как вы уже заметили, LazyLoading и (частично не актуально начиная с 6й версии ангуляра) грамотное расставление импортов.
IliaTrifonov Автор
20.02.2018 18:26Если вы приведете примеры действительно оправданного использования Single Page для веб-сайтов, которые нужно индексировать, то я соглашусь с тем, что такой вариант не очень и поменяю свою мнение:) Пока я лично считаю, что SPA не нужно использовать повсеместно. А там, где его использование необходимо, то индексация поисковиком и SEO оптимизация отходит на второй план.
sergeyZ
20.02.2018 20:05На asp.net core SSR в angular работает из коробки, просто нужно его включить. Выше я привел ссылку на документацию, если интересно как оно работает.
mokeev1995
20.02.2018 20:47хорошая документашка, но правильно ли я понимаю, что это всё ещё в статусе beta/rc?
sergeyZ
20.02.2018 22:40Да, rc2. Релиз будет вместе со следующей версией ASP.NET Core 2.1 т.к. теперь шаблоны проектов включены в официальный репозиторий aspnet.
navix
20.02.2018 20:41Не важно на чем написан API, даже если это сторонний сервис. Node в проде тут выступит как прокси (без всякого отношения к бекенду или самому Angular-приложению), который соберет данные с нужных эндпоинтов и отрендерит html.
mokeev1995
20.02.2018 20:47Node участвует, главным образом, как среда выполнения js-кода :)
а так да, в случае Node.js+angular обычно используется express, в случае asp net core — NodeServices, вроде как (а под капотом поднимается процесс ноды и она всё обрабатывает и возвращает вёрстку готовую).
просто под ASP.NET Core + angular ещё ведь обычно предполагается, что будут использовать всякие razor helpers, передачу данных из controller-а во вьюхи и т.д., чтоб использовать эту связку по максимуму.
или я не прав где-то?
sergeyZ
Проблемы, решение которых вы описываете в статье, были характерны для старых шаблонов, созданных еще для .NET Core 1.0. Команда ASP.NET Core хорошо поработала, теперь client не прибит гвоздями к серверу, рекомендую использовать template от команды asp.net. Можно использовать angular-cli. А вся необходимая настройка состоит из пары строк:
Вся магия в пакете Microsoft.AspNetCore.SpaServices.Extensions
IliaTrifonov Автор
Спасибо за совет! Однако упоминаний про это расширение я ни разу не встретил, когда пытался разобраться с темой. Надеюсь, что этот вариант удобнее и еще проще того, что я описал. Посмотрю.
sergeyZ
Вот документация: https://docs.microsoft.com/en-us/aspnet/core/spa/angular (если ссылка перестанет работать, вот еще одна)
IliaTrifonov Автор
В целом эта штука повторяет тоже самое, что я и описал в посте:) Единственные различие, что сам app находится внутри .NET Core проекта и простая настройка занимает чуть больше строк кода.
Расскажите, в чем профит вообще этого шаблона, кроме того что создается все одной командой? Пока на первый взгляд ничего существенно полезного не увидел.
sergeyZ
Создание одной командой уже само по себе неплохо. Для меня главное
преимущество — возможность использовать стандартную cookie-based авторизацию из asp.net core identity. Плюс возможность делать так:
Страницы авторизации и оплаты — это asp.net вьюхи — можно не переживать за кражу паролей и кредитных карт с помощью внедрения зловредного кода в node_modules (проверить все зависимости, которые тащит за собой ангуляр, а тем более сторонние модули просто невозможно). Плюс не авторизованный пользователь даже не получит кода нашего клиентского приложения.
Второй плюс — ноль самодельных костылей, которые надо потом поддерживать, все используемые функции — часть проекта ASP.NET Core и поддерживаются его командой.
IliaTrifonov Автор
Поставил бы плюс, если бы мог:) Спасибо, за разъяснение.
У вас нет случайно ссылок на гитхаб или статьи, где есть такое «смешивание» asp.net view и angular страниц? Как сделать страницу авторизации и перекинуть потом в приложение понимаю. Но как внедрить страницу оплаты — что-то не очень. Единственное, что пока приходит в голову — это просто перенаправлять с приложения на отдельную страницу. Существуют варианты, как подгружать вьюхи внутрь Angular приложения?
sergeyZ
Не видел таких проектов на гитхабе, я знаю только один boilerplate с авторизацией, но там все сделано на клиенте, с другой стороны переделывать совсем немного.
Нужно, чтобы маршруты в angular router не совпадали с маршрутами в asp.net, а потом при переходе на страницу с url, которого angular не знает он сделает запрос на сервер и пользователь увидит нужную страницу. Я не встраиваю эти вьюхи в приложение в своём проекте, но это легко можно сделать с помощью iframe.