BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Angular & ASP.NET Core 3.0 - Deep Dive

Angular & ASP.NET Core 3.0 - Deep Dive

Leia em Português

Bookmarks

Key Takeaways

  • Multiple Angular applications can be integrated into an ASP.NET web site
  • Packaging Angular code as Web Components is a good way to bootstrap an Angular application
  • Web Components written in Angular can be easily integrated into ASP.NET views
  • Structuring Angular solution as a collection of Angular applications leads to better code reuse
  • Using Angular with ASP.NET is a robust platform for creating web applications

This article is part of our .NET educational series which explores the benefits of the technology and how it can help not only traditional .NET developers but all technologists that need to bring robust, performant and economical solutions to market.

With the release of .NET Core 3.0, Microsoft has the next major version of the general purpose, modular, cross-platform and open source platform that was initially released in 2016. .NET Core was initially created to allow for the next generation of ASP.NET solutions but now drives and is the basis for many other scenarios including IoT, cloud and next generation mobile solutions. Version 3 adds a number of oft-requested features such as support for WinForms, WPF and Entity Framework 6.

 

Challenge

The easiest way to get started using Angular and ASP.NET Core is to use a Visual Studio template provided by Microsoft. This template gets you up and running rather quickly but suffers from one big limitation - Angular takes over the UI leaving ASP.NET in the background, serving up an API. If you want some pages to be served by .NET and other pages to be served by Angular, you need to duplicate the look and feel and the menu structure in both ASP.NET Core and Angular. Alternatively, you can leave the entire UI to be served by a single Angular app, but then you have to implement all the pages including trivial, static content such as Contact As, Licensing, etc. in Angular SPA.

The setup I want is an ASP.NET site serving as a portal and then embed Angular artifacts into ASP.NET pages. Generally speaking, there are two architectural design patterns. Under the first design pattern, I have an Angular app with routing I want to embed into an ASP.NET view with Angular providing a sub-menu and the ASP.NET site providing the top-level menu. Under the second design pattern, I have Angular components that don't warrant a fully-fledged Angular app, but yet there is a need to embed them in ASP.NET views. For example, suppose I wanted to embed a component that displays current time into an ASP.NET view. It's easy to develop such a component in Angular but it’s tricky to embed it into MVC views. Lastly, I want to achieve as much code re-use as possible - I want to be able to reuse components among Angular applications as well as to embed the same components into ASP.NET views. This article demonstrates how to bootstrap your ASP.NET and Angular projects to accommodate these architectural design patterns. If you want to see the final code, please refer to the Multi App Demo repository on GitHub.

To recap, here is what we want to build:

  • An ASP.NET Core web site that acts as a portal with a menu structure where each menu opens up an MVC view.
  • Ability to host one or more Angular SPAs within that website.
  • Ability to reuse some of the components from those SPAs in ASP.NET views.

Implementation Overview

  • We will create an ASP.NET MVC website using .NET Core (version 3.0 at the time of this writing).
  • We will create an Angular project using Angular CLI and integrate (dev workflow, prod build, publish, etc.) that project within the ASP.NET project.
  • We will bootstrap the Angular project as Web Components (aka. Custom Elements, aka. Angular Elements).
  • We will use Angular CLI to generate applications. These applications will reference the root Angular project for re-usable components.
  • We will embed Angular apps in ASP.NET Views using iframes pointing to the same domain.
  • IFrames will use JavaScript to re-size with content, and we will integrate ASP.NET routing with iframed   Angular apps, so it's possible to bookmark into routed Angular views.
  • To host Angular components in ASP.NET views, we will package those components as Web Components.

Create ASP.NET Core MVC Project

I am using Visual Studio 2019 Community Edition, a free download from Microsoft. Starting with 2019, the wizard for choosing templates is different from previous versions, but regardless which version you have, the steps are about the same.

  • Go to Create a new Project.
  • Select ASP.NET Core Web Application.
  • Select the name and location for the project (I called mine MultiAppDemo).
  • Choose ASP.NET Core (in my case, it's version 3.0).
  • Choose ASP.NET Model-View-Controller (for simplicity sake, select No Authentication, so VS doesn't generate artifacts irrelevant for this walk-through).

Your project view in the Solution Explorer should look something like this:

Since we used the ASP.NET MVC template and not the SPA template, we need to add Microsoft.AspNetCore.SpaServices.Extensions NuGet package. To install the package, open the Package Manager Console and run the following statement:

Install-Package Microsoft.AspNetCore.SpaServices.Extensions

Create Angular Project

Make sure you have the following software installed (all free):

  • Visual Studio Code
  • Node.js
  • Angular CLI. To install, go to the command prompt and execute this command:
    npm install -g @angular/cli

I am using Angular v8. Using an earlier version is okay as long as it has access to the createCustomElement API. Those capabilities are available in Angular v7 as well.

To create an Angular solution for use in our ASP.NET MVC project, open the command prompt and navigate to the folder that contains the project file (extension .csproj) for your MVC project. Once there, create the Angular project by executing this command from the prompt:

ng new Apps

Choose N for routing and CSS for styling.

Change directory to Apps, and type the following (including the trailing period):

code .

You should now have your Angular project opened in Visual Studio Code.

Bootstrap Angular Elements

The idea is to use this root project as a repository for re-usable components that will be available to other Angular applications (we will create them later) as normal Angular components and to the MVC views as Web Components (aka Angular Elements).

So, what is a web component? Here is a definition from webcomponents.org:

Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps.

Angular provides a way to package Angular components as web components via an API called Angular Elements. For instance, if you create an Angular component that shows the current time and bootstrap this component as Angular element current-time, you can then include <current-time /> tag in plain HTML pages. More information is available on the official Angular site.

Open your Angular project in VS Code. Open a terminal window and type a command to generate the clock component and to add that component to app.module.ts:  

ng g c current-time

You should now have a folder called current-time under src/app with several files that make up your clock component. Change your app/current-time/current-time.component.html to have the following markup:

 <p>{{ time }}</p>

Change your app/current-time/current-time.component.ts to have the following code:

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-current-time',
  templateUrl: './current-time.component.html',
  styleUrls: ['./current-time.component.css']
})
export class CurrentTimeComponent implements OnInit, OnDestroy {
  timer: any;
  time: string

  constructor() { }

  ngOnInit() {
    var setTime = () => {
      var today = new Date();
      this.time = ("0" + today.getHours()).slice(-2) + ":" +
                  ("0" + today.getMinutes()).slice(-2) + ":" +
                  ("0" + today.getSeconds()).slice(-2);
    };
    setTime();
    this.timer = setInterval(setTime, 500);
  }

  ngOnDestroy(){
    if (this.timer){
      clearTimeout(this.timer);
    }
  }
}

The implementation is rather simple. We have a timer that fires every half a second. The timer updates the time property with a string representing the current time, and the HTML template is bound to that string.

Paste the following styles into your app/current-time/current-time.component.css

p {
    background-color: darkslategray;
    color: lightgreen;
    font-weight: bold;
    display: inline-block;
    padding: 7px;
    border: 4px solid black;
    border-radius: 5px;
    font-family: monospace;
}

Now, save all the modified files and let's bootstrap this clock component as a web component:

  • Open a new terminal window inside Visual Studio Code.
  • Add Angular Elements libraries and polyfills. To do that type the following command at the terminal window:
    ng add @angular/elements
  • Navigate to src/app/app.module.ts
  • If it's not there already, add the following import statement at the top of app.module.ts:
    import { createCustomElement } from '@angular/elements';
  • Add Injector to the import from @angular/core:
    import { NgModule, Injector } from '@angular/core';
  • Replace bootstrap: [AppComponent] with
    entryComponents: [ClockComponent]
  • Lastly, add the constructor and ngDoBootstrap to the AppModule class.
constructor(private injector: Injector) {
}

ngDoBootstrap(){
  customElements.define('current-time', createCustomElement(CurrentTimeComponent,
                                                      {injector: this.injector}));
}

There is one more thing we need to do now that will be necessary later when we try to import CurrentTimeComponent in a different Angular application. We need to export this component from the module. To do that, add the exports property just above providers:

exports: [
    CurrentTimeComponent
],

Your entire app.module.ts should look like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';

import { AppComponent } from './app.component';
import { createCustomElement } from '@angular/elements';
import { CurrentTimeComponent } from './current-time/current-time.component';

@NgModule({
  declarations: [
    AppComponent,
    CurrentTimeComponent
  ],
  imports: [
    BrowserModule
  ],
  exports: [
    CurrentTimeComponent
  ],
  providers: [],
  entryComponents: [CurrentTimeComponent]
})
export class AppModule {
  constructor(private injector: Injector) {
  }

  ngDoBootstrap(){
    customElements.define('current-time', createCustomElement(CurrentTimeComponent,  
                                                        {injector: this.injector}));
  }
}

Now, let's test that our solution works. Go to src\index.html and replace <app-root></app-root> with <current-time></current-time>. Type ng serve --open from the terminal window to run the project. You should now see the current time showing in your browser.

Use Web Components in ASP.NET Project

The next step is to make our current-time component available in the ASP.NET MVC Core Project. To do that, open the ASP.NET project in Visual Studio. Paste the following code before the closing </body> tag in Views/Shares/_Layout.cshtml

<environment include="Development">
    <script type="text/javascript" src="http://localhost:4200/runtime.js"></script>
    <script type="text/javascript" src="http://localhost:4200/polyfills.js"></script>
    <script type="text/javascript" src="http://localhost:4200/styles.js"></script>
    <script type="text/javascript" src="http://localhost:4200/scripts.js"></script>
    <script type="text/javascript" src="http://localhost:4200/vendor.js"></script>
    <script type="text/javascript" src="http://localhost:4200/main.js"></script>
</environment>
<environment exclude="Development">
    <script asp-src-include="~/Apps/dist/core/runtime-es2015.*.js" type="module"></script>
    <script asp-src-include="~/Apps/dist/core/polyfills-es2015.*.js" type="module"></script>
    <script asp-src-include="~/Apps/dist/core/runtime-es5.*.js" nomodule></script>
    <script asp-src-include="~/Apps/dist/core/polyfills-es5.*.js" nomodule></script>
    <script asp-src-include="~/Apps/dist/core/scripts.*.js"></script>
    <script asp-src-include="~/Apps/dist/core/main-es2015.*.js" type="module"></script>
   <script asp-src-include="~/Apps/dist/core/main-es5.*.js" nomodule></script>
</environment>

The previous code segment shows two blocks, one for development and one for non-development. When we are in development, our Angular project that hosts web components will be running on port 4200, started from VS Code. When we are in production, the Angular project will be compiled into wwwroot/apps/core folder with javascript files named with hashes appended. In order to properly reference the files, we need to use asp-src-include tag helpers.

Next, in _Layout.cshtml, add <current-time></current-time> right after the </main> closing tag.

To test that our development configuration works:

  • Go to VS Code where your Angular project is opened, and type this command at the terminal prompt: ng serve --liveReload=false
  • Go to Visual Studio where your ASP.NET project is opened and hit F5 to run the project.

Your ASP.NET site should open up and you should see the current time component displayed on every page.

Create Angular Application

Web Components are great and might be the future of web UIs, but as of today, there is still a place for bootstrapping Angular projects as Single Page Applications (SPAs).

Angular is designed around the concept of modules and some of the features, most notably routing, are aligned to modules, not components. When mixing Angular and ASP.NET development, my goal is to host Angular apps in MVC views. I want the ASP.NET MVC to provide the top-level menu structure and the SPAs to provide their own menu and routing structure that lives within a larger MVC application. Moreover, I want to achieve code reuse where code can be shared between multiple SPAs within the solution as well included in the non-Angular pages as web components.

The first step is to create a new application in Angular. The easiest way to do that is to use the Angular CLI (command line interface). If not already, open your Angular project in VS Code, and start a new terminal window. At the terminal, execute this command:

ng g application App1 --routing=true

This will generate a new Angular application under Apps\projects\App1 with routing module configured. Let's generate two components and setup the routes, so we can have somewhere to route to. Execute from the terminal:

ng g c Page1 --project=App1

ng g c Page2 --project=App1

You should now see two new component folders, page1 and page2, under Apps/Projects/App1/src/app.

Let's now set up routing for these components. Change your app.component.html under
Apps/Projects/App1/src/app to have this marketup:

<h2>App1</h2>
  <a routerLink="/page1" routerLinkActive="active">Page1</a>
  <a routerLink="/page2" routerLinkActive="active">Page2</a>
<router-outlet></router-outlet>

And update your app-routing.module.ts under Apps/projects/App1/src/app with the following code:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';

const routes: Routes = [
  {path: '', redirectTo: 'page1', pathMatch: 'full'},
  {path: 'page1', component: Page1Component},
  {path: 'page2', component: Page2Component}];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

This is just standard routing code. For a review of Angular routing, visit this page.

Let's now test that our new application is configured correctly. Open a new terminal window and type this command:

ng serve App1 --port 4201 --open

Your browser should open and you should see something like this

 
Notice we are using port 4201 which is different from the port we used for the root Angular project. Each app you create will need to be served on a different port in development, but in non-development environments, all apps; ASP.NET and Angular, will run on the same port.

Now, one objective of this demo is to achieve code re-use. Let's now reuse the Angular component from the base project in App1. To do that, include an import of the CurrentTimeComponent in the App1's main module.

Go to app.module.ts under Apps/projects/App1/src/app and add the following import statement:

import { CurrentTimeComponent } from '../../../../src/app/current-time/current-time.component';

What's happening here is we are importing the CurrentTimeComponent from the root project. Alternatively, you could import the entire AppModule from the root project.

Next, add CurrentTimeComponent to the list of declarations:

  declarations: [
    AppComponent,
    Page1Component,
    Page2Component,
    CurrentTimeComponent
  ],

Now, go to app.component.html in App1, and add the tag for the current time, right below the router outlet.

<h2>App1</h2>
<a routerLink="/page1" routerLinkActive="active">Page1</a>
<a routerLink="/page2" routerLinkActive="active">Page2</a>
<router-outlet></router-outlet>
<app-current-time></app-current-time>

Notice we used the Angular tag (app-current-time) for this component, not the web component tag name (current-time). This is because we are including the component as the Angular component. App1 has no knowledge of this Angular component used elsewhere as a web component.

Save all files and check the browser. Your App1 page should now show the current time component.

Integrating App1 into ASP.NET MVC as a SPA

The last thing we want to do in this walk-through is to incorporate App1 into ASP.NET MVC app as a Single Page Application. We want the following features:

  • The SPA should be embedded into one of the MVC views.
  • It should be possible to deep-link to a page in the SPA.
  • Live reload should be supported.

First, let's set up a regular MVC view called App1 off the Home Controller.

In your MVC project, go to Controllers/HomeController.cs and add the following code:

[Route("app1/{*url}")]
public IActionResult App1(string url)
{
    return View("App1", url);
}

The {*url} construct in the Route attribute tells ASP.NET to capture everything to the right of the /app1/ segment in the url variable. We will then pass this down to the Angular application.

Now, right-click on the View() token, and select Add View. Call the view App1 and click the Add button. This should create a file called App1.cshtml in Views/Home. Make sure the file has the following mark up:

@{
    ViewData["Title"] = "App1";
}

This is the view for App1.

Go to Shared/_Layout.cshtml and add a link to this view right under the link to the Privacy view. The easiest way is to copy the privacy link markup and replace the word Privacy with the word App1.

<ul class="navbar-nav flex-grow-1">
     <li class="nav-item">
          <a class="nav-link text-dark" asp-area="" asp-controller="Home"
                    asp-action="Index">Home</a>
     </li>
     <li class="nav-item">
          <a class="nav-link text-dark" asp-area="" asp-controller="Home"
                    asp-action="Privacy">Privacy</a>
      </li>
      <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home"
               asp-action="App1">App1</a>
      </li>
</ul>

While we are in _Layout.cshtml let's make one more change. Let's add a bit of markup around the <current-time> web component to have a visual indication this is a web component and not an Angular component. To do that add <HR> and a comment:

<div class="container">
     <partial name="_CookieConsentPartial" />
     <main role="main" class="pb-3">
         @RenderBody()
     </main>
     <hr />
     This is a web component<br />
     <current-time></current-time>
</div>

Next, let's test the app. Hit F5 and make sure you can navigate to App1 view via the App1 link.

The next step is to embed the App1 application into the App1 MVC view. We are going to use an iframe that points to a URL on the same domain. Using an iframe has the benefit of encapsulating App1 in its own container, but presents two challenges:

  • iframe needs to be dynamically resized when its content changes.
  • The address bar of the top window has to change when a user navigates inside the Angular app.

We will solve both of these challenges using JavaScript. This is only possible because the iframe points to the same domain; thus, avoiding cross-domain restrictions.

But, before we do that, we still need to do a few more modifications in the .NET code.

First, let's configure App1 in Startup. Open Startup.cs and add the following code to the Configure method:

app.Map("/apps/app1", builder => {
    builder.UseSpa(spa =>
    {
        if (env.IsDevelopment())
        {
            spa.UseProxyToSpaDevelopmentServer($"http://localhost:4201/");
        }
        else
        {
            var staticPath = Path.Combine(
                Directory.GetCurrentDirectory(), $"wwwroot/Apps/dist/app1");
            var fileOptions = new StaticFileOptions
                { FileProvider = new PhysicalFileProvider(staticPath) };
            builder.UseSpaStaticFiles(options: fileOptions);

            spa.Options.DefaultPageStaticFileOptions = fileOptions;
        }
    });
});

This code tells the .NET core runtime to map the app to /apps/app1 path, to proxy to port 4201 in development and to expect the compiled files to be available in wwwroot/apps/app1 in non-development environments.

But we don't want our app to be served to a user from /apps/app1. We want our app to be available when a user navigates to the App1 view, which can be /home/app1 or just /app1 URLs.

This is where we are going to use an iframe. Open App1.cshtml and add the following markup:

<iframe src="/apps/app1/@Model" class="app-container" frameborder="0" scrolling="no"></iframe>

Note the @Model construct. This is mapped to {*url} in the component -- we are passing the portion of the path to the right of app1 from the top window to the iframe, so routing works inside the Angular app.

At this point, we can test the app. Go to VS Code and execute the following serve command from an available terminal window:

ng serve App1 --port 4201 --servePath / --baseHref /apps/app1/ --publicHost http://localhost:4201

This command starts App1 on port 4201. It sets the base HREF since we know it's going to be served from apps/app1, and it instructs Angular to use localhost:4201 for live reload instead of using relative URLs.

Go to Visual Studio and hit F5. After the ASP.NET site comes up in your browser, navigate to the App1 menu. If you see a screen similar to the following, it means the app is wired up correctly.

While the App1 Angular application does appear inside the App1 view, the content is cut off; moreover, if you click on Page 1 and Page 2 links, you can see the navigation is working inside the Angular component but the top address bar in the browser does not reflect the current state of navigation. Let's fix both of these issues.

To resize the iframe with content on startup and any time the content of the iframe changes, we will use a JavaScript component called iFrame Resizer created by David Bradshaw.

There are three steps we need to do in order to make this component work.

In _Layout.cshtml, paste the following script tag right above the script tag pointing to site.js

<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js">
</script>

Add the following line of code to site.js, located in wwwroot/js.

$('.app-container').iFrameResize({ heightCalculationMethod: 'documentElementOffset' });

Next, go to VS Code and right above the closing </body> tag, add the following script tag to Index.html located in Apps/projects/App1/src:

<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.contentWindow.min.js">
</script>

Save all files and let's retest the app. App1 should now look like this:

 

Notice how the content is no longer cut-off. The nice thing about iFrame Resizer is it will keep the iframe resized to fit the content after the initial load of the iframe.

Let's now address the issue of the address bar not being updated when the Angular router links are clicked. It's not updated is because App1 is running inside of an iframe. The iframe's address is changing but we don't see it because the address bar we see is for the top browser window.

Remember we already have the code to capture the path to the right of the /app1 URL segment into {*ulr} variable and pass it to the iframe. What we need to add is code to go the other way - when routing is engaged inside the Angular app, we want the changes to be propagated to the top address bar.

To do that we need to add code to the routing module in the App1 application.

Open app-routing.module.ts located in Apps/projects/App1/src/app. Add the following code in the constructor of AppRouting Module:

constructor(private route:Router){
  var topHref = window.top.location.href != window.location.href ?
                window.top.location.href.substring(0,
                                         window.top.location.href.indexOf('/app1') + 5) :
                null;
  this.route.events.subscribe(e => {
    if(e instanceof NavigationEnd){
      if (topHref){
        window.top.history.replaceState(window.top.history.state,
                                        window.top.document.title, topHref + e.url);
      }
    }
  });
}

This code determines if the app is running in the iframe by comparing the href of the top window and of the current window. If the app is running in the iframe, the code saves the HREF of the top window in a local variable, but trims the part of the HREF to the right of the /app1 segment. Next, the code taps into the NavigationEnd event and appends the routed URL to the top window's HREF.

You will also need to add Router and NavigationEnd to the imports. Your entire app-routing.module.ts should look like this:

import { NgModule } from '@angular/core';
import { Routes, RouterModule, Router, NavigationEnd } from '@angular/router';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';

const routes: Routes = [
  {path: '', redirectTo: 'page1', pathMatch: 'full'},
  {path: 'page1', component: Page1Component},
  {path: 'page2', component: Page2Component}];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {
  constructor(private route:Router){
    var topHref = window.top.location.href != window.location.href ?
                  window.top.location.href.substring(0,
                                     window.top.location.href.indexOf('/app1') + 5) :
                  null;
    this.route.events.subscribe(e => {
      if(e instanceof NavigationEnd){
        if (topHref){
         window.top.history.replaceState(window.top.history.state,
                                         window.top.document.title, topHref + e.url);
        }
      }
    });
  }
}

To test the app, start it from Visual Studio. Click on Page1 or Page 2 links. Observe the top URL is now changing. You can also copy the modified URL and paste it into a separate window, and App1 will route to the component specified in the top URL.

Adjust Publish Settings

There is one last thing to do. We need to modify the project file to incorporate the Angular build tasks into the publish process. To do that, go to your ASP.NET project, right click on the project file, select Edit <YourProjectName>.csproj. Your project file should look similar to this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <TypeScriptToolsVersion>3.3</TypeScriptToolsVersion>
    <SpaRoot>Apps\</SpaRoot>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <WarningLevel>0</WarningLevel>
  </PropertyGroup>

  <ItemGroup>
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0" />
  </ItemGroup>

  <Target Name="PublishApps" AfterTargets="ComputeFilesToPublish">
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod --outputPath=./dist/core" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build App1 -- --prod --base-href=/apps/app1/ --outputPath=./dist/app1" />

    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>wwwroot\%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

The interesting part here is the Target tag. We instruct the build process to run npm install, then build both Angular projects, then copy the dist folder output to wwwroot folder of the ASP.NET site.

To test that our publish configuration works:

  • Right click on the ASP.NET project name in Visual Studio.
  • Go to Publish.
  • Under Pick a publish target, choose Folder.
  • Click the Publish button.

At the end of the process, you should see the full path of the folder where the new files were published in the Output window. To test the published site:

  • Open the publish folder in the command window.
  • Type: dotnet <Your Project Name>.dll
  • Go to your browser and open http://localhost:5000

Conclusion

We created an ASP.NET site, integrated it with two Angular projects and embedded Angular artifacts into MVC views. If you want to play around with the solution, I encourage you to clone the project from GitHub. Try adding App2 and serve it from a different MVC view or try creating more web components.

About the Author

Evgueni Tsygankov has been writing software for the past 30 years, from Commodore 64 in the 80s to cloud computing nowadays. He currently leads a team of developers as part of Effita, a software firm based in St Louis, Missouri. In his spare time, Evgueni spends time with his two kids and plays ice hockey and soccer.

 

This article is part of our .NET educational series which explores the benefits of the technology and how it can help not only traditional .NET developers but all technologists that need to bring robust, performant and economical solutions to market.

With the release of .NET Core 3.0, Microsoft has the next major version of the general purpose, modular, cross-platform and open source platform that was initially released in 2016. .NET Core was initially created to allow for the next generation of ASP.NET solutions but now drives and is the basis for many other scenarios including IoT, cloud and next generation mobile solutions. Version 3 adds a number of oft-requested features such as support for WinForms, WPF and Entity Framework 6.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • Issue with sample

    by Leonid Shraybman,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Evgueni, thanks for the article. For whatever reason, I am getting an issue when attempting to render the control in the ASP.NET Core page.

  • Cannot install Microsoft.AspNetCore.SpaServices.Extensions

    by Chan Xavier,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Hi,
    Thanks for your article.
    I cannot install the Spa Service for I get the following error:
    Microsoft.AspNetCore.SpaServices.Extensions 3.1.0 is not compatible with netcoreapp3.0

    May I know how I should deal with it?

  • Securing Angular Routes with .Net Identity Autentication

    by Hermann Valderrama,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    It works like a charm.
    It will be fantastic if you get to the next step, securing the routes using the asp .net core authentication. I have tried this, but i have not been successfull with that.

  • Re: Cannot install Microsoft.AspNetCore.SpaServices.Extensions

    by Paul Guernsey,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    [Problem]
    app.Map("/apps/app1", builder => {
    builder.UseSpa(spa =>
    {...


    [2020/08/17]
    The requested page is not available for ASP.NET Core 3.1. You have been redirected to the newest product version this page is available for.

    [Fix ]

    1. In NuGet Package Manager, add a reference to ISpaBuilder from
    Microsoft.AspNetCore.SpaServices.Extensions.dll

    SpaApplicationBuilderExtensions.UseSpa(IApplicationBuilder, Action<ISpaBuilder>) Method

    docs.microsoft.com/en-us/dotnet/api/microsoft.a...

    2. add using statements:

    using System.IO;
    using Microsoft.Extensions.FileProviders;
    ______</ispabuilder>

  • How to pass input values to Angular elements(Web Components) from ASP.Net MVC app.

    by Ashok Davuluri,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Hi,

    I have tried your approach. its worked well. now I need to pass some data from the ASP.Net app to angular elements. how can we pass? how can we deal with events/data moving from angular to .net app and .net app to angular?

    Thanks in advance. please help me out.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT