Руководство по работе ASP.NET Core с Angular2

В этом руководстве, я бы хотел Вам показать, как настроить приложение используя ASP.NET Core API для интерфейса Angular2. Вы познакомитесь с Startup класс в MVC, настройки зависимости в Angular2 с NPM, конфигурации SystemJS, компоненты и сервисы Angular2, и как подключать их вместе.

Проект, который Вы можете использовать как шаблон, Вы можете его найти в GitHub.

  1. Базовая серверная часть API ASP.NET Core
    1. Startup класс
  2. Интерфейс
    1. NPM пакеты
    2. Index.html
      1. Head
      2. Body
    3. Конфигурационный файл SystemJS
    4. Инициализация AppModule
    5. Расширение приложения новыми компонентами
    6. HomeComponent
  3. Запуск приложения
  4. Добавления сервиса
  5. ApiController
  6. Итог

Базовая серверная часть API ASP.NET Core

Я начну с подготовки нашей серверной части сервиса. Для этого, откроем Visual Studio и создадим пустой веб проект ASP.NET Core. После этого, Вы должны получить простое приложение для отображения «Hello World». Давайте модифицируем его под наши нужды.

Создание нового проекта ASP.NET Core

По сколку мы хотим использовать MVC для API и обслуживать статические файлы, Вам нужно добавить следующие пакеты зависимости в файл project.json:

"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0"

Startup класс

Теперь давайте изменим наш Startup класс, и скажем asp.net использовать MVC:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .AddMvcOptions(options =>
            {
                options.CacheProfiles.Add("NoCache", new CacheProfile
                {
                    NoStore = true,
                    Duration = 0
                });
            });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvcWithDefaultRoute();
    }
}

Как вы могли увидеть, я добавил настройку MVC в двух местах. В первом месте я добавил сервисы MVC, а в следующим запуск их. Я также добавил запрет кэширования в заголовки во все что будет запускать MVC. Этим я предотвратил кэширование вызовов API (Общая проблема в IE).

Теперь давайте добавим index.html файл, который будет хостом Angular2 приложения. Это просто сделать, добавив этот кусок кода в метод конфигурацию Startup класса:

app.Use(async (context, next) =>
{
    await next();

    if (context.Response.StatusCode == 404 &&
        !Path.HasExtension(context.Request.Path.Value) &&
        !context.Request.Path.Value.StartsWith("/api/"))
    {
        context.Request.Path = "/index.html"; 
        await next();
    }
});

Что оно делает? Если MVC возвращает 404 и запрашиваемый путь не имеет расширения (html, jpg и т.д.), это вернет index.htm файл. Он ловит все, для проверки этого, введите в браузере, например, такие ссылки:

  • /home
  • /product/123
  • /contact

Он будет обслуживать этот начальный файл и angular приложение будет поднимать его оттуда.

Интерфейс - Angular2 Single Page Application

NPM пакеты

На данный момент, мы имеем готовую серверную часть. Теперь давайте займёмся интерфейсом. Для начала, нам нужно получить все пакеты Angular2 которые нам нужны из npm. Для этого, Вам необходимо добавить такие зависимости в файл package.json:

"dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",

    "core-js": "2.4.1",
    "reflect-metadata": "0.1.3",
    "rxjs": "5.0.0-beta.12",
    "systemjs": "0.19.27",
    "zone.js": "0.6.23"
}

Примечание: Для новых версий angular, вы всегда можете взять пакеты версий здесь

Так же нам понадобиться ещё некоторые инструменты разработки для нашего приложения:

 "devDependencies": {
    "del": "latest",
    "gulp": "latest",
    "gulp-sass": "latest",
    "gulp-sourcemaps": "latest",
    "gulp-tslint": "latest",
    "gulp-typescript": "latest",
    "jasmine-core": "latest",
    "karma": "latest",
    "karma-chrome-launcher": "latest",
    "karma-coverage": "latest",
    "karma-jasmine": "latest",
    "karma-mocha-reporter": "latest",
    "karma-phantomjs2-launcher": "latest",
    "karma-story-reporter": "latest",
    "lite-server": "latest",
    "path": "latest",
    "phantomjs2": "latest",
    "require-dir": "latest",
    "systemjs-builder": "latest",
    "tslint": "latest",
    "typescript": "latest"
}

Для загрузки всех этих пакетов Вам, нужно будет выполнит такую команду:

npm install --save-dev

После этого вы должны получить папку с именем node_modules с JavaScript пакетами и установленными инструментами в вашей директории пользователя. Теперь мы должны сказать MVC, что следует использовать эту директорию. По умолчанию, MVC использует только папку wwwroot для статических файлов.

string libPath = Path.GetFullPath(Path.Combine(env.WebRootPath, @"..\node_modules\"));
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(libPath),
    RequestPath = new PathString("/node_modules")
});

Также следует добавить node_modules в часть настройки нашего index.html файла, поэтому мы получим нормальную ошибку 404, если при запросе на не существующий файл в директории node_modules:

if (context.Response.StatusCode == 404 &&
    !Path.HasExtension(context.Request.Path.Value) &&
    !context.Request.Path.Value.StartsWith("/node_modules/") &&
    !context.Request.Path.Value.StartsWith("/api/"))
{
    context.Request.Path = "/index.html"; 
    await next();
}

Index.html

На данный момент, у нас все готово, чтобы начать строить наш angular2 приложение. Для первого шага, давайте создадим index.html файл. Готовый файл доступный в GitHub. В целом, файл состоит из двух важных частей.

Head

Содержит ссылки на скрипт и базовый заголовок

<script src="/node_modules/core-js/client/shim.min.js"></script>
<script src="/node_modules/zone.js/dist/zone.js"></script>
<script src="/node_modules/reflect-metadata/Reflect.js"></script>
<script src="/node_modules/systemjs/dist/system.src.js"></script>

<script src="/app/systemjs.config.js"></script>

<base href="/">

Body

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

<script>
    System.import('/app/main')
        .then(null, console.error.bind(console));
</script>

<app>Loading...</app>

Конфигурационный файл SystemJS

По умолчанию, angular2 использует модули загрузки SystemJS для управления зависимостями. Я не буду вдаваться подробно как это работает так как это не входить в рамки данной статьи. В конфигурационном файле список пакетов, с свойствами, которые говорят, как загружать эти пакеты:

(function(global) {

    var map = {
        'rxjs': '/node_modules/rxjs',
        '@angular': '/node_modules/@angular',
        'app': "/app"
    };

    var packages = {
        'app':                        { main: 'main.js',  defaultExtension: 'js' },
        'rxjs':                       { defaultExtension: 'js' }
    };

    var ngPackageNames = [
        'common',
        'compiler',
        'core',
        'http',
        'forms',
        'platform-browser',
        'platform-browser-dynamic',
        'router',
        'testing',
        'upgrade'
    ];

    // Individual files (~300 requests):
    function packIndex(pkgName) {
        packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
    }

    // Bundled (~40 requests):
    function packUmd(pkgName) {
        packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
    }
    // Most environments should use UMD; some (Karma) need the individual index files
    var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
    // Add package entries for angular packages
    ngPackageNames.forEach(setPackageConfig);
    var config = {
        map: map,
        packages: packages
    };
    System.config(config);

})(this);

Инициализация AppModule

Сейчас, настало время подготовки и начальной загрузки нашего приложения. Первой задачей будет создания файла app.module.ts:

@NgModule({
    declarations: [
        AppComponent,
        HomeComponent
    ],
    imports:      [
        BrowserModule,
        HttpModule,
        routing
    ],
    providers: [
    ],
    bootstrap:    [AppComponent],
})
export class AppModule {}

Он по существу говорит Angularу, какой компонент, модуль и сервис Вы собираетесь использовать вашем приложение.

После этого мы может инициализировать наше приложение в файле main.ts:

platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));j

Теперь мы готовы создать файл AppComponent.ts:

@Component({
    selector: "app",
    template: `<router-outlet></router-outlet>`,
    directives: [ROUTER_DIRECTIVES]
})
export class AppComponent { }

Это будет основным компонентом нашего приложения. Как Вы видите, это шаблон содержит только router-outlet – это место, где вес Ваш будет контент происходить рендеринг. Это говорит, мы можем сейчас создать определённые маршруты в файле app.routes.ts:

const appRoutes: Routes  = [
    { path: "", redirectTo: "home", pathMatch: "full" },
    { path: "home", component: HomeComponent }
];

export const routing = RouterModule.forRoot(appRoutes);

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

Расширение приложения новыми компонентами

Если Вы хотите добавить новые маршруты в бедующем Вы просто должны выполнить следующие шаги:

1. Создать компонент, например, ProductListComponent

2. Задекларировать новый компонент в файле main.ts

3. Добавить новый маршрут в файле app.routes.ts

HomeComponent

У нас уже все настроено, и пришло время создать компонент HomeComponent:

@Component({
    selector: 'app-home',
    template: `Hello World`
})
export class HomeComponent {
    
}

Запуск приложения!

Предыдущий шаг был последним перед тем как запускать наше маленькое приложение. Сделавши это, нам необходимо для начала построить интерфейсную часть. Если вы посмотрите в репозиторий GitHub, вы найдете файл gulpfile.js. Так как мы используем TypeScript для angular2, нам нужно транспилировать его в JavaScript. Мы также копируем другие файлы с папки Frontend в папку wwwroot, который будет обслуживается ASP.NET Core API. Здесь мы будем использовать 2 команды для этого этапа:

  • gulp build – построение всего и завершение
  • gulp watch – построение и запуск отслеживания изменений, при изменениях любых файлов в папке Frondend будет происходить перестройка всех файлов

Я использую Gulp для обработки файлов по двум причинам. Прежде все??о, это очень быстро. Режим отслеживания изменений — это замечательно для разработки. Файлы обрабатываются в течении миллисекунд, поэтому, когда вы обновите Ваш браузер, Вы всегда получите новые файлы. Во-вторых, это позволяет строже контролировать все что должно быть сделано, поэтому это дает Вам одну папку для исходных файлов, а другую для построенных файлов.

Когда вы строите интерфейсную часть, вы можете запустить приложение в VisualStudio. Если все в порядке, вы должны уведёт это:

Hello world

Мы имеем приложение ASP.NET Core которое обслуживает Angular2 приложение. Следующий шаг добавить Angular2 сервис который будет получать некоторые данные от API.

Добавление сервиса

Лучший способ реализации связи с API — через службы. Этим путем вы создадите модульное приложение и упростите тестирование. Для создания сервиса, создайте файл с именем hello.service.ts:

@Injectable()
export class HelloService {

    constructor(private http: Http) {
    }

    greet(name: string): Observable<string> {
        return this.http
            .get(`/api/hello?name=${name}`)
            .map(res => <string> res.text());
    }
}

Вы должны заполнить о некоторых вещах, которые здесь используются:

  • @Injectable() вы сообщаете в систему зависимости, что вы будете использовать этот класс как зависимость в других классах
  • Вы подключаете Http-модуль в конструктор
  • Возврат Observable из метода

Указанный метод не возвращает текстовую строку – это возвращение Observable как тип строки. Это потому, что все коммуникации с Http-модулем производится асинхронно. Таким образом, чтобы использовать, то, что должно вернутся от API, Вам необходимо подписаться до этого Observable. Вот как я модифицировал home.component.ts:

export class HomeComponent {
    constructor(private helloService: HelloService) {
    }

    ngOnInit() {
        this.greet("Michal");
    }

    greeting: string;

    greet(name: string): void {
        this.helloService
            .greet(name)
            .subscribe(data => this.greeting = data);
    }
}

Было добавлено несколько вещей:

Конструктор, который получает HelloService как зависимость

  • ngOnInit() – это метод, который следить когда компонент будет готовый для использования. В этом случае он просто является способом greet метода.
  • greet метод – этот метод используется как greet метод HelloService сервиса и подписан на получение его результата
  • Метод подписки Observable get функции, которая вызывается, когда результат Observable готовый для использования. Это типичный способ делать вещи асинхронными – в другом мире это методы с обратным вызовом.

ApiController

Для полноты работы, нам также нужно простой API контролер в нашей серверной части:

public class ApiController : Controller
{
    [HttpGet]
    [Route("/api/hello")]
    public string Hello(string name)
    {
        return $"Hello {name}";
    }
}

С этими всеми изменениями, наше приложение отобразить очень прекрасное приветствие:

Итог

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

Данная статья является переводом, оригинальный текст Вы можете прочитать здесь

Автор перевода: Grygorii Shkliaruk

Автор оригинала: Michal Dymel

P.S. Данная статья является первой статьей которую я сам перевел, так что прошу сильно не ругать, все замечание прошу оставлять в комментарии.

Только авторизованные пользователи могут оставлять комментарии

Чтобы оставить комментарий Вам нужно Зарегистрироваться или Войти в систему