I’m trying to deploy my application which consists of Angular and ASP.NET. I’ve managed to successfully publish the app with docker. When I launch application with docker and call any method from swagger I get 404. How can I fix this?
Maybe it is because docker is launched on localhost and tries to make request to localhost, not the container. Nevertheless, I think that if the localhost was launched successfully the requests should also work(?).
One more question is – how do I launch my Angular app with docker? When I run the docker, api is the only project that is launched. You may notice that I’ve excluded Angular app from csproj because I’ve added additional steps to build it in dockerfile.
Please feel free to ask me any details.
[Route("api/[controller]")]
[ApiController]
public class PingController : ControllerBase
{
[HttpGet("ping")]
public int Ping()
{
return 1;
}
}
dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
# Setup NodeJs
RUN apt-get update &&
apt-get install -y wget &&
apt-get install -y gnupg2 &&
wget -qO- https://deb.nodesource.com/setup_16.x | bash - &&
apt-get install -y build-essential nodejs
# End setup
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["SelfWebsiteApi/SelfWebsiteApi.csproj", "SelfWebsiteApi/"]
RUN dotnet restore "SelfWebsiteApi/SelfWebsiteApi.csproj"
COPY . .
WORKDIR "/src/SelfWebsiteApi"
RUN dotnet build "SelfWebsiteApi.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "SelfWebsiteApi.csproj" -c Release -o /app/publish
#Angular build
FROM node as nodebuilder
# set working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY SelfWebsiteApi/SelfWebsiteAngular/package.json /usr/src/app/package.json
RUN npm install
RUN npm install -g @angular/cli --unsafe
# add app
COPY SelfWebsiteApi/SelfWebsiteAngular/. /usr/src/app
RUN npm run build --prod
#End Angular build
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
RUN mkdir -p /app/SelfWebsiteAngular/dist
COPY --from=nodebuilder /usr/src/app/dist/. /app/SelfWebsiteAngular/dist/
ENTRYPOINT ["dotnet", "SelfWebsiteApi.dll"]
csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<SpaRoot>SelfWebsiteAngular</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules**</DefaultItemExcludes>
<UserSecretsId>57cd274b-65dd-4fa0-938b-cdb4fb135876</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules**" />
</ItemGroup>
<!--<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build - -prod" />
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>-->
</Project>
program.cs:
var builder = WebApplication.CreateBuilder(args);
var origin = builder.Configuration.GetValue<string>("SelfWebsiteAngular:Name");
var angularLink = builder.Configuration.GetValue<string>("SelfWebsiteAngular:Link");
builder.Services.AddCors(options =>
{
options.AddPolicy(origin, builder =>
{
builder.WithOrigins(angularLink)
//.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<SelfWebsiteContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SelfWebsiteDatabase")));
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration.GetValue<string>("Settings:ServerLink"),
ValidAudience = builder.Configuration.GetValue<string>("Settings:ServerLink"),
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
builder.Configuration.GetValue<string>("Settings:Authorization:SymmetricSecurityKey"))),
};
});
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddTransient<ITokenService, TokenService>();
builder.Services.AddTransient<IAuthService, AuthService>();
builder.Services.AddTransient<IResumeService, ResumeService>();
builder.Services.AddTransient<ILinkService, LinkService>();
builder.Services.AddTransient<ISectionService, SectionService>();
builder.Services.AddSingleton<IMapperProvider, MapperProvider>();
builder.Services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "SelfWebsiteAngular/dist";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseStaticFiles();
//if (!app.Environment.IsDevelopment())
//{
app.UseSpaStaticFiles();
//}
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "SelfWebsiteAngular";
//if (app.Environment.IsDevelopment())
//{
spa.UseAngularCliServer(npmScript: "start");
// spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
//}
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(origin);
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2
Answers
Swagger is, by default, only available when the environment is ‘Development’. Containers, by default, aren’t development.
The code that does that is this in
Program.cs
There are multiple ways to make Swagger available in a container.
You can permanently make it available by removing the
if
in the above code so it becomesYou can also set the environment in the image to Development by setting
ASPNETCORE_ENVIRONMENT
like thisA third option is to pass the environment variable on your
docker run
command by adding an-e ASPNETCORE_ENVIRONMENT=Development
option to the command.Which solution you should choose depends on how permanently you want to make Swagger available.
Try
and also then
because for me worked normally without this configuration in debug mode but it didn’t work in Docker.