You can regenerate the same project selecting .NetCore MVC so long as the rest of the detail stays the same
Console app gets a token successfully but returns 401 Unauthorised when connecting to the api endpoint from a remote ip. Works fine if connecting via localhost. I think this may be something to do with IdentityServer being configured to use .AddDeveloperSigninCredentials but I have no idea how I would configure this to work in a multi tenant environment where the host is at https://api.{tenant_name}.domain.com
For info:
The issue was because I had used https://www.nuget.org/packages/Hangfire.Dashboard.Customize/ to customise the Hangfire Dashboard.
It's middleware and but doesn't allow any other middleware to run futher down the chain.
Replied to you via email
When I do that I get ERR_TOO_MANY_REDIRECTS
As that folder already contains web.config assume merging the two?
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\projectname.Web.Host.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
<!--ProjectGuid: 9fc37c62-2105-4d32-9724-7323b959504b-->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\projectname.Web.Host.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
<mimeMap fileExtension="woff" mimeType="application/font-woff" />
<mimeMap fileExtension="woff2" mimeType="application/font-woff" />
</staticContent>
<!-- IIS URL Rewrite for Angular routes -->
<rewrite>
<rules>
<rule name="Angular Routes" enabled="true" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
<defaultDocument>
<files>
<clear />
<add value="index.html" />
</files>
</defaultDocument>
</system.webServer>
</location>
</configuration>
<!--ProjectGuid: 9fc37c62-2105-4d32-9724-7323b959504b-->
web.config in wwwroot dir
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
<mimeMap fileExtension="woff" mimeType="application/font-woff" />
<mimeMap fileExtension="woff2" mimeType="application/font-woff" />
</staticContent>
<!-- IIS URL Rewrite for Angular routes -->
<rewrite>
<rules>
<rule name="Angular Routes" enabled="true" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
<defaultDocument>
<files>
<clear />
<add value="index.html" />
</files>
</defaultDocument>
</system.webServer>
</configuration>
.net core Angular Single Project 9.0.1
This works fine locally but when publishing to IIS any deep link i.e /account/login returns a 404.
It should all as the provided
app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; await next();
} });
should handle those 404s
namespace PROJECT.Web.Startup { public class Startup { private const string DefaultCorsPolicyName = "localhost";
private readonly IConfigurationRoot _appConfiguration;
private readonly IWebHostEnvironment _hostingEnvironment;
public Startup(IWebHostEnvironment env)
{
_hostingEnvironment = env;
_appConfiguration = env.GetAppConfiguration();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//MVC
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
}).AddNewtonsoftJson();
services.AddSignalR();
//Configure CORS for angular2 UI
services.AddCors(options =>
{
options.AddPolicy(DefaultCorsPolicyName, builder =>
{
//App:CorsOrigins in appsettings.json can contain more than one address with splitted by comma.
builder
.WithOrigins(
// App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
_appConfiguration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
if (bool.Parse(_appConfiguration["KestrelServer:IsEnabled"]))
{
ConfigureKestrel(services);
}
IdentityRegistrar.Register(services);
AuthConfigurer.Configure(services, _appConfiguration);
//Identity server
if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
{
IdentityServerRegistrar.Register(services, _appConfiguration, options =>
options.UserInteraction = new UserInteractionOptions()
{
LoginUrl = "/UI/Login",
LogoutUrl = "/UI/LogOut",
ErrorUrl = "/Error"
});
}
if (WebConsts.SwaggerUiEnabled)
{
//Swagger - Enable this line and the related lines in Configure method to enable swagger UI
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo() { Title = "inteliProCS API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.ParameterFilter<SwaggerEnumParameterFilter>();
options.SchemaFilter<SwaggerEnumSchemaFilter>();
options.OperationFilter<SwaggerOperationIdFilter>();
options.OperationFilter<SwaggerOperationFilter>();
options.CustomDefaultSchemaIdSelector();
}).AddSwaggerGenNewtonsoftSupport();
}
//Recaptcha
services.AddreCAPTCHAV3(x =>
{
x.SiteKey = _appConfiguration["Recaptcha:SiteKey"];
x.SiteSecret = _appConfiguration["Recaptcha:SecretKey"];
});
if (WebConsts.HangfireDashboardEnabled)
{
//Hangfire(Enable to use Hangfire instead of default job manager)
services.AddHangfire(config =>
{
config.UseSqlServerStorage(_appConfiguration.GetConnectionString("Default"));
});
}
if (WebConsts.GraphQL.Enabled)
{
services.AddAndConfigureGraphQL();
}
if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
{
services.AddAbpZeroHealthCheck();
var healthCheckUISection = _appConfiguration.GetSection("HealthChecks")?.GetSection("HealthChecksUI");
if (bool.Parse(healthCheckUISection["HealthChecksUIEnabled"]))
{
services.Configure<HealthChecksUISettings>(settings =>
{
healthCheckUISection.Bind(settings, c => c.BindNonPublicProperties = true);
});
services.AddHealthChecksUI();
}
}
//Configure Abp and Dependency Injection
return services.AddAbp<inteliProCSWebHostModule>(options =>
{
//Configure Log4Net logging
options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig(_hostingEnvironment.IsDevelopment()
? "log4net.config"
: "log4net.Production.config")
);
options.PlugInSources.AddFolder(Path.Combine(_hostingEnvironment.WebRootPath, "Plugins"), SearchOption.AllDirectories);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
//Initializes ABP framework.
app.UseAbp(options =>
{
options.UseAbpRequestLocalization = false; //used below: UseAbpRequestLocalization
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseStatusCodePagesWithRedirects("~/Error?statusCode={0}");
app.UseExceptionHandler("/Error");
}
app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; await next();
} });
// app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "index.html" }});
app.UseStaticFiles();
app.UseRouting();
app.UseCors(DefaultCorsPolicyName); //Enable CORS!
app.UseAuthentication();
app.UseJwtTokenMiddleware();
if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
{
app.UseJwtTokenMiddleware("IdentityBearer");
app.UseIdentityServer();
}
app.UseAuthorization();
using (var scope = app.ApplicationServices.CreateScope())
{
if (scope.ServiceProvider.GetService<DatabaseCheckHelper>().Exist(_appConfiguration["ConnectionStrings:Default"]))
{
app.UseAbpRequestLocalization();
}
}
if (WebConsts.HangfireDashboardEnabled)
{
//Hangfire dashboard &server(Enable to use Hangfire instead of default job manager)
app.UseHangfireDashboard(WebConsts.HangfireDashboardEndPoint, new DashboardOptions
{
// Authorization = new[] { new AbpHangfireAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) }
});
var options = new BackgroundJobServerOptions
{
Queues = new[] { "WindowsService", "default" }
};
app.UseHangfireServer(options);
RecurringJob.AddOrUpdate<IHangFireNotificationAppService>("Daily CSCS/ ECS Card Check", dd => dd.CheckCSCSECSCardValid(), Cron.Daily(8));
RecurringJob.AddOrUpdate<IHangFireAppService>("Check access revocation", dd => dd.CheckForAccessRevocation(), "*/5 * * * *");
RecurringJob.AddOrUpdate<IHangFireAppService>("Monthly Co2Report", mm => mm.MonthlyCo2ReportJob(), Cron.Daily(21));
RecurringJob.AddOrUpdate<IHangFireAppService>("Weekly Co2Report", ww => ww.WeeklyCo2ReportJob(), Cron.Weekly(DayOfWeek.Friday, 21));
RecurringJob.AddOrUpdate<IHangFireAppService>("Daily Co2Report", dd => dd.DailyCo2ReportJob(), Cron.Daily(21));
RecurringJob.AddOrUpdate<IHangFireAppService>("Get Job Request", rs => rs.GetJobRequestResult(), "*/20 * * * *");
RecurringJob.AddOrUpdate<IHangFireNotificationAppService>
("Send Warning Notification", r => r.SendNotificationForHangFire(),
"*/10 * * * *");
}
if (bool.Parse(_appConfiguration["Payment:Stripe:IsActive"]))
{
StripeConfiguration.ApiKey = _appConfiguration["Payment:Stripe:SecretKey"];
}
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<AbpCommonHub>("/signalr");
endpoints.MapHub<ChatHub>("/signalr-chat");
endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}
});
if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
{
if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksUI:HealthChecksUIEnabled"]))
{
app.UseHealthChecksUI();
}
}
if (WebConsts.SwaggerUiEnabled)
{
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger();
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint(_appConfiguration["App:SwaggerEndPoint"], "API V1");
options.IndexStream = () => Assembly.GetExecutingAssembly()
.GetManifestResourceStream("inteliProCS.Web.wwwroot.swagger.ui.index.html");
options.InjectBaseUrl(_appConfiguration["App:ServerRootAddress"]);
}); //URL: /swagger
}
}
private void ConfigureKestrel(IServiceCollection services)
{
services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
{
options.Listen(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 443),
listenOptions =>
{
var certPassword = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Password");
var certPath = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Path");
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath, certPassword);
listenOptions.UseHttps(new HttpsConnectionAdapterOptions()
{
ServerCertificate = cert
});
});
});
}
}
}
This doesn't work either
I couldn't get it to work. Ended up creating a public facing unauthenticated controller to handle incoming webhooks.