Intro
I had written Angular app and ASP.NET Core app separately.But because I had had to use some internal api at my work, I tried to integrate them.
Prepare
- Angular (CLI ver. 8.3.21)
- ASP.NET Core (ver. 3.1.101)
So there hadn't been so much documents.
[Announcement] Obsoleting Microsoft.AspNetCore.SpaServices and Microsoft.AspNetCore.NodeServices #12890
Thus, I had generated sample program of Angular template.
And I had followed it to integrate apps.
dotnet new angular -n AngularSample
Change Angular app
Because of Microsoft.AspNetCore.SpaServices.Extensions, I hadn't needed changing the Angular app.Output Path
The output path hadn't been same as Angular template's one.angular.json (Angular CLI)
...
"outputPath": "dist/{PROJECT-NAME}",
...
angular.json (ASP.NET Core)
...
"outputPath": "dist",
...
So I changed "dist".Move project files
In the Angular template project, the Angular project files in the ASP.NET Core project.And I could build them together. So I move the Angular project files.
L UpgradeSample - ASP.NET Core project's root directory L UpgradeSample L clients - Angular project's root directory L ... L ... L UpgradeSample.csproj L global.json L UpgradeSample.slnBecause the project's directory had been changed, I execute "npm install" again on the new Angular project's root directory.
Change ASP.NET Core app
Add Microsoft.AspNetCore.SpaServices.Extensions
For using SPA Service, I added Microsoft.AspNetCore.SpaServices.Extensions by NuGet.UpgradeSample.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
...
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.1" />
</ItemGroup>
...
</Project>
For using Angular, I just needed to add Microsoft.AspNetCore.SpaServices.Extensions.But because I had used PostgreSQL, I had also added EntityFrameworkCore etc..
For building JavaScript app
For building JavaScript app, I had to add more to csproj.UpgradeSample.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
<SpaRoot>clients\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<!-- Set this to true if you enable server-side prerendering -->
<BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Most of all the code for JavaScript, I had copied from the Angular template sample.I would study about them after writing this.
Add SPA middlewares to Startup.cs
All I needed to add were SpaStaticFiles and Spa.Startup.cs
using Files;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Models;
using UpgradeSample.Models;
using UpgradeSample.Products;
using UpgradeSample.Users;
namespace UpgradeSample
{
public class Startup
{
private IConfigurationRoot Configuration { get; }
private static readonly string AllowedOrigins = "_allowedOrigins";
...
public void ConfigureServices(IServiceCollection services)
{
// DB Connect
services.AddDbContext<UpgradeSampleContext>(options =>
options.UseNpgsql(Configuration["DbConnect"]));
// Identity
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddUserStore< ApplicationUserStore>()
.AddEntityFrameworkStores<UpgradeSampleContext>()
.AddDefaultTokenProviders();
/*services.AddCors(options =>
{
options.AddPolicy(AllowedOrigins,
builder =>
{
builder.WithOrigins("http://localhost:5000",
"http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();;
});
});*/
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "clients/dist";
});
// DI
services.AddSingleton<ILocalFileAccessor, LocalFileAccessor>();
services.AddScoped<IProductDao, ProductDao>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IUserService, UserService>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseSpaStaticFiles();
}
app.UseStaticFiles();
//app.UseCors(AllowedOrigins);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "clients";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "s");
}
});
}
}
}
When I developed separated two apps, because their server's hosts hadn't been the same.So I had had to add CORS.
But now, the Angular app worked on Kestrel as same as ASP.NET Core's.
So I could remove it.
And in the Angular template sample, "app.UseSpaStaticFiles()" had been written after "app.UseStaticFiles()".
I had written them in reverse, I couldn't find any bad effects although I hadn't known this was right or not.
Build
There hadn't been any changes as ASP.NET Core MVC app.dotnet run
ActionFilter
Because I had wanted to do something before showing the page of Angular, I tried to add ActionFilter.But failed.
SampleActionFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using System.Threading.Tasks;
using System;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
public class SampleActionFilter: IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("HELLO WORLD!!");
await next();
}
}
...
namespace UpgradeSample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers(options =>
options.Filters.Add(typeof(SampleActionFilter)));
}
...
Only I accessed to teh URLs what had been routed by the controller of the ASP.NET Core, the ActionFilter worked.So I had thought I couldn't do this.
If I would find the solution, I would write here :)
Resources
Use the Angular project template with ASP.NET Core - Microsoft DocsFilters in ASP.NET Core - Microsoft Docs
Use JavaScript Services to Create Single Page Applications in ASP.NET Core | Microsoft Docs
[Announcement] Obsoleting Microsoft.AspNetCore.SpaServices and Microsoft.AspNetCore.NodeServices #12890
JavaScriptServices/src/Microsoft.AspNetCore.SpaServices at master · aspnet/JavaScriptServices · GitHub
Angular & ASP.NET Core 3.0 - Deep Dive - InfoQ
ASP.NET Core MVC で大きく変わったフィルタについて調べた - しばやん雑記
コメント
コメントを投稿