Publishing .NET Core 3.1 React application on IIS 8.5 results in API routes not being respected - asp.net-core-mvc

I'm currently trying to mount a .NET Core 3.1 MVC application created with dotnet new react. The application works perfectly fine when running it via Visual Studio Code. However, when publishing with dotnet publish -c Release -r win81-x64 and mounting the application in IIS 8.5, the routes for the APIs stop working, sending all requests to the React page.
Currently my configuration in Startup.cs looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
And the code in a test controller I made looks like this:
namespace Application.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase {
private readonly ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<string>> GetTest()
{
return "Test";
}
}
}
I also tried creating a new React project and mounting it, and that does seem to work. However, the Startup.cs file has the exact same configuration, so I can't find anything to modify on my application.

I figured it out. Turns out the first version I uploaded had some issues that prevented routes from working properly, and while I immediately fixed it, the Service Worker that the default .NET project has cached the broken API point as part of the site. Apparently this bypasses having the 'Disable Cache' tick box in developer tools.

Related

Why does Visual Studio skip some directories like Controllers, Classes, and App_Start when publishing to Azure?

I have a classic ASP.Net forms based website where I have added s set of API hits based on the OAuthAuthorizationServerProvider class. It works great locally, now I trying to publish it to an Azure App Service. The ASP.Net portion of the website works great, no problems. But there are 2 obvious issues with the API side of the website. 1) By default the Classes, Controllers and App_Start directories do not get pushed to the App Service when publishing the website. 2) After manually publishing the missing directories the API service does not respond to the hits. POST http://test.azurewebsites.net/api/v1/token responds with a 404 (Not Found) error.
I know someone is going to ask for the contents of the App_Start files so here they are.
RouteConfig.cs
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.FriendlyUrls;
namespace App_Start
{
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
var settings = new FriendlyUrlSettings();
settings.AutoRedirectMode = RedirectMode.Permanent;
routes.EnableFriendlyUrls(settings);
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
Startup.cs
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
[assembly: OwinStartup(typeof(App_Start.Startup))]
namespace App_Start
{
public partial class Startup
{
public void Configuration(IAppBuilder App)
{
App.UseCors(CorsOptions.AllowAll);
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/v1/token"),
Provider = new ApiAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(5),
//AllowInsecureHttp = true
};
App.UseOAuthAuthorizationServer(options);
App.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
}
WebApiConfig.cs
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace App_Start
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
I don't think I'm missing something in the startup for the API hits, but that is most likely the cause of my 2nd issue.
Thanks, Russ
By default the Classes, Controllers and App_Start directories do not get pushed to the App Service when publishing the website.
I have deployed the ASP.Net Web API to Azure App Service. Even Iam unable to see the Controllers, classes and Config files folders.
When we deploy the Web App, all the source files are compiled into the dll files.
Deployed Folder Structure
No need to push all the files and folders manually.
POST http://test.azurewebsites.net/api/v1/token responds with a 404 (Not Found) error.
We need to enable Routing in Configure() method inStartup.cs file.
In Startup.cs, add the below lines
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

Lamar with ASP.NET Core 6.0 and controller registration

I am trying to properly structure my ASP.NET Core 6.0 service registrations and therefore would like to move the registration into its own class.
Unfortunately, the code below doesn't work. It seems the controllers are not registered as swagger complains about No operations defined in spec!. If I move the call to registry.AddControllers() back to my ConfigureRegistry method, it works.
What am I doing wrong here?
var builder = WebApplication.CreateBuilder(args);
ConfigureConfiguration(builder.Configuration);
ConfigureRegistry(builder.Host, builder.Configuration);
var app = builder.Build();
ConfigureMiddleware(app, app.Services, app.Environment);
ConfigureEndpoints(app, app.Services, app.Environment);
app.Run();
void ConfigureConfiguration(ConfigurationManager configuration)
{
}
void ConfigureRegistry(ConfigureHostBuilder host, ConfigurationManager config)
{
host.UseLamar((context, registry) =>
{
// registry.AddControllers();
// registry.AddEndpointsApiExplorer();
// registry.AddSwaggerGen();
// registry.AddAutoMapper(typeof(Program));
registry.IncludeRegistry<WebRegistry>();
// ...
registry.IncludeRegistry<ApplicationRegistry>();
registry.IncludeRegistry<InfrastructureRegistry>();
});
}
void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services, IWebHostEnvironment environment)
{
if (environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseRouting();
}
void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services, IWebHostEnvironment environment)
{
app.MapControllers();
}
public class WebRegistry : ServiceRegistry
{
public WebRegistry()
{
this.AddControllers();
this.AddEndpointsApiExplorer();
this.AddSwaggerGen();
this.AddAutoMapper(typeof(Program));
For<ICategoryViewService>().Use<CategoryViewService>();
}
}

IHostedService .NET 6 not starting when deployed

I have an API which contains a HostedService that is built in VS2022 with .Net 6.
When I run locally the service is called as expected and everything works fine but when deplyed, the service doesn't seem to start.
I have tried many different configurations and even tried using a background service but it all has the same result. Here is my code:
I have an existing app build in VS2019 .Net Core 3.1 that has a HostedService and is working fine. I noticed that when I converted my .Net Core app to .Net 6, the service did not start when I deployed so I decided to just build a little app to try and find what's causing the issue.
Program.cs
using HostedServices.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithMachineName());
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHostedService<MainService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Here is the Hosted Service
namespace HostedServices.Services
{
public class MainService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<MainService> _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public MainService(ILogger<MainService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"Test Hosted Service Started {DateTime.Now}.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_executingTask = DoWorkAsync(_stoppingCts.Token);
}
private async Task DoWorkAsync(CancellationToken token)
{
_logger.LogInformation(
"Doing work: {0}", DateTime.Now);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
}
Everything runs fine locally but when I deploy it, the service doesn't seem to start, no log files are generated and I can't seem to find any errors reported.
Any ideas?
We had the same problem and resolved it by following IIS settings:
Under "Page" Advanced Settings: "Preload Enabled" must be set to "true" (this forces app to run after every app pool recycle and on startup)
Under "App Pool" Advanced settings: "Start mode" must be set to "AlwaysRunning"
"Page" in IIS must have http allowed (since preload enabled works only on http and not via https)
The problem is that while IHostedService start when the site starts when does IIS start the site? By default, it doesn't until a request is made. If your site goes idle for a while IIS will kill the thread as well.
IIS has a feature you can enable called "Application Initialization".
You then need to configure the Application Pool to always start (startMode="AlwaysRunning").
Lastly, you configure the site's application to preload which sends a fake HTTP request to the site on startup (preloadEnabled="true").
The docs for this are here: https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-application-initialization

OData Controller withing Asp.Net Core MVC application

I'm working on a project in ASP .NET Core 3.1 MVC now I want to add some API controllers to return list of objects.
For this I want to use OData Controller version 8.0.0 so I can get quarriable data to improve performance on large data tables
I'm new in ASP .NET Core and OData. can anybody explain how to configure my project's Startup file so I can run both MVC and OData controllers same time.
Kindly share some example code
Firstly, you have a MVC project, since MVC project can also expose API, so OData should also work for MVC project. Firstly, assuming you've integrate ef core and in my workaround, I followed this document to create database and data management view for a model.
Then let's add OData. Install this nuget package: Microsoft.AspNetCore.OData, modify your startup.cs file, please see the Configuration and GetEdmModel method.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using WebMvcNet5.Data;
using WebMvcNet5.Models;
using Microsoft.OData.ModelBuilder;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
namespace WebMvcNet5
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddControllers().AddOData(opt => opt.EnableQueryFeatures().AddRouteComponents("odata", GetEdmModel()));
services.AddDbContext<WebMvcNet5Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebMvcNet5Context")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
//My model is Movie, and what I set "GetMovie" here means I need to create a controller named "GetMovieController"
builder.EntitySet<Movie>("GetMovie");
return builder.GetEdmModel();
}
}
}
Then this is my controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using WebMvcNet5.Data;
namespace WebMvcNet5.Controllers
{
[Route("odata/[Controller]")]
public class GetMovieController : Controller
{
private readonly WebMvcNet5Context _context;
public GetMovieController(WebMvcNet5Context context)
{
_context = context;
}
[EnableQuery]
public IActionResult Get()
{
return Ok(_context.Movie);
}
}
}
My test result:
I have managed to fix my issue to run Web Application which exposes OData APIs
Issue was in Startup.cs file
I'm using Asp.Net Core 3.1 and Microsoft.AspNetCore.OData v7.3.0
my Startup.cs file code is:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<AppUser, AppRole>(opt =>
{
opt.User.RequireUniqueEmail = true;
})
//.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//add services
services.RegisterServices();
services.AddScoped<ViewRendererService>();
services.AddMvc()
.AddMvcOptions(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddOData();
services.AddRouting();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext dataContext)
{
if (env.EnvironmentName == "Development")
{
dataContext.Database.Migrate();
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRequestLocalization();
app.UseMvc(routes =>
{
routes.Select().Filter().OrderBy().Expand().Count().SkipToken().MaxTop(null);
routes.MapODataServiceRoute("odata", "api", GetEdmModel());
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "Finance",
template: "{area:exists}/{controller=Account}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("ProductApi");
builder.EntitySet<ProductUOM>("ProductUomApi");
ActionConfiguration action = builder.EntityType<Product>().Action("GetUOM");
action.Parameter<long>("id");
action.ReturnsCollectionFromEntitySet<Product>("Product");
return builder.GetEdmModel();
}
}
Hope this will help others

IdentityServer4 discoveryDocument returns 404

I'm following this tutorial to setup Ids4, my steps were the same as the video but I'm getting a 404 when I navigate to https://localhost:5443/.well-known/openid-configuration.
Things I did :
created a new Web APP
-added the IdentityServer4 nuget package
-edited the startup as follows:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddInMemoryClients(new List<Client>())
.AddInMemoryIdentityResources(new List<IdentityResource>())
.AddInMemoryApiResources(new List<ApiResource>())
.AddInMemoryApiScopes(new List<ApiScope>())
.AddTestUsers(new List<TestUser>())
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseIdentityServer();
app.UseAuthentication();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
If I navigate to localhost, it displays a hello world as it should, but the discovery document returns a 404. Any ideas as to why?
The order of your middlewares is incorrect. Here's how it should be
Also, to determine what's going wrong, you must check for errors in the Visual Studio Output window

Resources