.NET Core Controller API call to Azure SQL Database works in localhost but not in deployed Azure Web App - asp.net-core

Summary:
I have a .NET Core project that uses the React web app template for the front end. This app uses Entity Framework Core to connect to an Azure SQL Database. I used the Db-Scaffold command to generate my models (just one table at the moment), and created a controller to return this table. Locally, this works fine and the table (JSON) is returned at localhost/api/Users. However when I deploy the website to Azure (CD pipeline is VS 2017 - > GitHub -> DockerHub -> Azure Web App), navigating to mysite.azurewebsites.net/api/Users just renders the login page (React) of my app.
Attempts:
I have tried:
Adding a connection string as a shared value in Azure (named DefaultConnection)
Adding all the outbound IP's of the Azure Web App to the Azure SQL Whitelist
Running the following in the consoles of the web app
fetch('api/users')
This just returns:
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
I have also tried changing database values and refreshing the local version to make sure it was not just a cached page and sure enough the changes were reflected locally.
I also set ASPNETCORE_ENVIRONMENT in the Web App settings in Azure to Production. Although when I go to the error message, page (through the console) I get this:
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<p>
<strong>Request ID:</strong> <code>0HLK3RLI8HD9Q:00000001</code>
</p>
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Code
UsersController.cs
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly AccrubalanceDbContext _context;
public UsersController(AccrubalanceDbContext context)
{
_context = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Users>> Get()
{
return await _context.Users.ToListAsync();
}
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection":<MyConnectionStringGoesHere>
},
index.js (just in case React might be the routing problem)
const baseUrl = document.getElementsByTagName('base')
[0].getAttribute('href');
const rootElement = document.getElementById('root');
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App />
</BrowserRouter>,
rootElement);
registerServiceWorker();
Startup.cs (could be potentially problem with HTTP routing in Prod?)
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddDbContext<AccrubalanceDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment 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.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
Conclusion
In conclusion, I need this API call to work within the hosted Azure Web App like it does on my local machine. I know I am close since I got it to work locally, but I am missing something along the way to Azure. Any help or pointers you can provide would be great :)
I am still new to SO and took my time to do my best to format this correctly. I am open to constructive formatting critiques and suggestions to help me improve.
Edit:
As I mentioned before, I am using docker for CD/CI. So I ran my docker container locally and the api does not work there either. Docker throws this warning in the command window when I navigate to the apps home page.
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.
Edit 1 Determination
I also found this article which points to react routing being an issue. I have looked in Kudo in my Azure app and I do not have a web.config. Could potentially try adding on but I do not have the regular Windows UI since my app is a Linux server.
The container build acts like the Azure App does, may not be an Azure issue. Still unsure why docker is acting differently than running in VS.

Solution:
There is obviously some problem with Docker. Since it was becoming more of a headache then a help, I removed it from the deployment pipeline and just followed the instructions here. Once I did this deployment method, all the API's worked. Only downside is I had to make a new app in Azure.

Related

How to deploy ASP Core Web API VueJS site to IIS

I am new to VueJS and SPAs in general. I have created a new ASP Core site with a WebAPI controller for data and a VueJS front end. I am now trying to deploy this site to IIS and I am not sure how to do it correctly. I created a new application in IIS with an application pool set to "no-managed-code" and set the physical location to the VueJS app /dist folder. The site is loading, but I'm getting 404's for all of my service calls. I assume this is because the root of the site is set to the VueJS app folder instead of the root of the ASP Core folder. How do I set this up correctly to serve my app from myServer/mySite and also have my service endpoints as myServer/mySite/api/myController/myAction?
Scenario: Your dotnet core app has the API endpoints and you want to host the client site SPA on the same site. API calls will go through to the dotnet app and any other request will serve the index.html of the SPA.
.NET core supports this scenario with the methods from Microsoft.AspNetCore.SpaServices namespace like UseSpa()
Also note that in .NET 5 these extensions are moving to separate package Microsoft.AspNetCore.SpaServices.Extensions. It is available now but not well documented.
Your build SPA should go in ClientApp/dist in this example
e.g.
using Microsoft.AspNetCore.SpaServices;
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
// In production, the SPA files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc();
// Must be near the end of the method because
// it will send any unhandled requests to index.html for SPA
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
// Development requests are send through to local node server
spa.UseProxyToSpaDevelopmentServer("http://localhost:8080/");
}
});
}
}

URL Rewrite exceptions for Blazor WebAssembly Hosted deployment

During development, i have used Swagger on the server side of my Blazor WebAssembly App. Always launching (debug) using kestrel instead of IIS Express.
Routing worked as expected, all my component routed properly and if i manually typed /swagger, i got to the swagger page. All good.
We have deployed under IIS on our pre-prod servers, the Server side and Blazor WebAssembly App (client) work as expected and are usable, however, my /swagger url gets rewritten (I assume) to go somewhere in my App instead of letting it go to Swagger, obviously there isn't any component that answers to /swagger.
My only guess is that, when hosted on IIS, the aspnet core app takes care of telling IIS what to rewrite and how (similar to the configs that could be provided thru a web.config for a "Standalone" deployment.)
I can't find how to specify exceptions, I've been following the doc at
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1#iis
Any idea how i could add an exception for /swagger ?
EDIT:
Turns out it works without issues in Chrome, only Firefox has the unwanted behavior. If i clear my cache, or use Incognito mode, the issue does not happen in Firefox. So, it seems that Firefox caches some stuff and tries to send my URL input to the Blazor Wasm instead of going thru to the server. I will debug some more with the dev tools and fiddler open to try and figure it out, will report back.
Turns out there this is part of the service-worker.js file that is published. It is different in dev than what gets published (which makes sense).
During my debugging i was able to reproduce the issue on all browsers (Edge, Chrome and Firefox), regardless of being in Incognito/Private mode or not.
Once the service-worker is running, it handles serving requests from cache/index.html of the Blazor WebAssembly app.
If you go into your Blazor WebAssembly Client "wwwroot" folder, you'll find a service-worker.js and a service-worker.published.js. In the service-worker.published.js, you will find a function that looks like this :
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/connect/')
&& !event.request.url.includes('/Identity/');
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
Simply following the instructions found in the code comments is gonna fix the issue. So we ended up adding an exclusion for "/swagger" like so :
&& !event.request.url.includes('/swagger')
Hopefully this post is useful for people who are gonna want to serve things outside of the service worker, not only Swagger.
Do you have UseSwagger first in your Startup.Configure method?
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YourAppName V1")
);
In Startup.ConfigureServices I have the Swagger code last.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(c =>
c.SwaggerDoc(
name: "v1",
info: new OpenApiInfo
{
Title = "YourAppName",
Version = "V1",
}));
}
This is working just fine for us.
Note: You must navigate to https://yourdomain/swagger/index.html

ASP.NET Core 2.2 Authentication not working after deploying the app - Nginx with mutliple apps

I'm trying deploy my application to our internal server with Nginx. It's an ASP.NET Core 2.2 Razor Pages site. I was asked to include some authentication for logging purposes. Eventually everything was working fine on my computer. I used this site to add cookie based authentication:
https://www.mikesdotnetting.com/article/335/simple-authentication-in-razor-pages-without-a-database
I did some modifications to handle a few more users within the OnPost() method. Though I don't think that would be the problem.
It might be important to mention this is not the only .net core app running on the server. The setup is similar to this:
app1: our.domain.com
app2: our.domain.com/app2 (this is the one I have problems with)
everything works properly except the login. When I try to log in, if the password and username is correct it gets redirected to the proper page, however it seems like there is no identity or it cannot find it afterwards.
On my first attempt, I found the following error in the kestrel service log:
fail: Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery[7]
I could make that disappear with using services.AddDataProtection(), however the problem is still the same, I get redirected - or get the error message if the login attempt is incorrect - but still can't access the authorised folder and e.g. the HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value returns null or empty.
I did a quick test and added the same login page and an authorised folder, and the other dependencies to the other app. And there it works. I didn't even included services.AddDataProtection() in the startup.cs. The login works perfectly. Though it's using .net core 2.1.
So it might have to do something with the rooting? Or I don't know. I'm totally lost. I'm not a full time developer, more like a hobbyist and I'm completely stuck at this moment. Maybe I messed up something within the startup.cs? Or I should add something else? Or is it something with the cookies handling? I did make a lot of searching, no luck so far.
Here is the relevant part of my startup.cs:
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.AddEntityFrameworkNpgsql()
.AddDbContext<Models.UserAccessDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("appConnection")))
.BuildServiceProvider();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// https://hanselman.com/blog/DealingWithApplicationBaseURLsAndRazorLinkGenerationWhileHostingASPNETWebAppsBehindReverseProxies.aspx
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.All;
options.AllowedHosts = Configuration.GetValue<string>("AllowedHosts")?.Split(';').ToList<string>();
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(cookieOptions =>
{
cookieOptions.LoginPath = "/";
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/admin");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDataProtection()
.SetApplicationName("app")
.PersistKeysToFileSystem(new DirectoryInfo(#"/var/dpkeys/"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment 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.Use((context, next) =>
{
context.Request.PathBase = new PathString("/app");
return next.Invoke();
});
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
Update:
One small detail. The cookie is created and I can see it within the Chrome's inspector. But the site/app doesn't see me as an authenticated user.
Seems I had problems with the cookie settings. One more difference between the two pages, that one if it uses font-awesome, which means I have some external cookies on my site. As a wild guess, first I set
options.MinimumSameSitePolicy = SameSiteMode.Lax;
since the other cookies had that mode.
And just like that it started to work on the hosting server as well! Now the app recognises the cookie created after the log in.

ASP.Net Core spa pre-rendering with angular universal works locally but not on server

I'm trying to set up angular universal on angular7 with asp.net core 2.1. The prerendering works perfectly on a local build but does not add code to the page source on a server in production with no errors or logs.
Due to the lack of logs I suspect this is because the server module is not loaded/ started but I have no idea why. Is this something that should be added into the web config?
I am using a windows shared plesk server which supports node.js and IIS Node. Here is a snippet from my Startup.cs
app.UseSpa(spa =>
{
spa.Options.StartupTimeout = new System.TimeSpan(0, 0, 1000);
spa.Options.SourcePath = "ClientApp";
spa.UseSpaPrerendering(options =>
{
options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.js";
options.BootModuleBuilder = env.IsDevelopment() ? new
AngularCliBuilder(npmScript: "build:ssr") : null;
options.ExcludeUrls = new[] { "/sockjs-node" };
});
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
I eventually realised I had the line app.UseStaticFiles(); as well as app.UseSpaStaticFiles in Startup.cs. Removing app.UseStaticFiles(); solved this issue. However, I am still not sure why this wasn't causing issues on a local build.
I believe you are misusing the prerendering middleware.
About prerendering: Represents the ability to build a Single Page Application (SPA) on demand so that it can be prerendered. This is only intended to be used at development time. In production, a SPA should already have been built during publishing.
You should hide that piece of code behind:
env.IsDevelopment()
Read more here

ASP.Net Core SignalR simple host - 404 error

I am wanting to have 1 simple console app host that is solely for self-hosting the SignalR component.
I have created an "Empty Web Application" using the template. I have created a very simple StartUp file that does not contain anything like MVC etc as it is not needed. However I am getting a 404 not found error from the browser when attempting to negotiate.
The Startup file is as follows:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseSignalR(routes =>
{
routes.MapHub<Masterhub>("masterhub");
});
}
}
As you can see, it is very basic, but as I don't want any MVC/Web API functionality, I didn't include all of that setup. There is setup for CORS and SignalR, that is all.
Is what I am attempting to do possible?
It turns out that the JavaScript file I was using from the web client to connect to the self-hosted SignalR Console Application, was the old full .Net version, and not the new version you can get from NPM.