Adding Web API as Application to Website in IIS, where does Frontend point to? - reactjs

I have two separate processes running for my website. The frontend is reactjs and the backend is asp.net core 6.
I have a reactjs website running on localhost:9000 on IIS on a server.
I've added the .net web api published files to the above website as an application with it's own application pool.
In the .env REACT_APP_API_URL: I don't know how to point to the API, before i just used localhost:5000, how do I change this to use the application url?
Tried: */api/controllername, localhost/api/controllername, IPADDRESS/api/controllername,
COMPUTERNAME/api/controllername etc.
Here is the Program.cs
using api;
using api.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseIIS();
builder.Services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowOrigin",
options =>
{
options.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "Project.Cookies";
options.ExpireTimeSpan = TimeSpan.FromHours(12);
options.SlidingExpiration = true;
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
options.Cookie.IsEssential = true;
});
builder.Services.AddDbContext<DbDataContext>();
builder.Services.AddScoped<IProjectService, ProjectService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || app.Environment.IsProduction())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Here is the web.config for the backend:
<?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="dotnet" arguments=".\api.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
<!--ProjectGuid: -->
And here is the web.config for the reactjs frontend:
<?xml version="1.0"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="React Routes" 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>
</system.webServer>
</configuration>
I'm in over my head on how to get these talking to each other.
I can run the dotnet from the commandline and have reactjs talk to that localhost on the server (this wont work when connecting to the website from outside the server). But I need these working in IIS.
Any help would be greatly appreciated.

The front end and the backend application should use same port. In your react project, you should use something like const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href'); or process.env.PORT. And your frontend will start with port which you create the main website. You can find it like below.
The main application and virtual application host in same site with different application. And they also should use same port. You can check my test result.
And you can open the frontend application by clicking the Browse *:8031 in the right panel in my picture.
You can access your api application via https://localhost:8031/api/{apiname or controller/action}.
In your case, it should be http://localhost:port/{hidden by red pen}api/controllername.

Related

How to deploy a NextJs SSR React app on Azure

I have been trying to deploy a Server-side rendered react app I built with NextJS on Azure. I set up the Azure pipeline and release successfully but after running it the app doesn't seem to load up when I went to the azure website URL. The build file content is different from a client rendered app. Please share a resource or explanation about deploying SSR React apps (on Azure).
I used this resource to set up the pipeline and I encountered no error but the URL is still not loading the app.
As mentioned by #James in the comment in Doris's answer, using a custom server.js in a Next.js app would make it slower in production because the routes aren't pre-built. Using next build followed by next start would solve this issue. But for you to be able to do that, you should not have a server.js.
And as per Microsoft documentation for deploying Node JS / Next JS application on Azure Linux web app, the recommended way is to use PM2 rather than using npm start (or) node server.js.
So you don't need server.js or web.config. All you need to do is to have a file called ecosystem.config.js with the below content
module.exports = {
apps: [
{
name: "my-nextJs-site",
script: "./node_modules/next/dist/bin/next",
args: "start -p " + (process.env.PORT || 3000),
watch: false,
autorestart: true,
},
],
};
and have the startup command for your Azure app to be this
pm2 --no-daemon start /home/site/wwwroot/ecosystem.config.js
and no change in your package.json scripts
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
Azure Windows App Service
pm2 is not available in Azure Windows Service - it uses IIS Server. Check out the following answer and its linked questions.
Other useful resources:
Deploying a Node.js application on Windows IIS using a reverse proxy
Deploying Nextjs on IIS server without custom server
Deploying Nextjs on Digital Ocean - App Platform
You need two file: server.js and web.config, and modify package.json like below. I've answered a question about deploy nextjs app step by step, you could have a look at this.
package.json modify.
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "node server.js"
server.js (create this file with the code below:)
const { createServer } = require('http')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = new URL(req.url, 'http://w.w')
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/a', query)
} else if (pathname === '/b') {
app.render(req, res, '/b', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
web.config (create this file with the code below:)
<?xml version="1.0" encoding="utf-8"?>
<!--
This configuration file is required if iisnode is used to run node processes behind
IIS or IIS Express. For more information, visit:
https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
-->
<configuration>
<system.webServer>
<!-- Visit http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on WebSocket support -->
<webSocket enabled="false" />
<handlers>
<!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
<add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<!-- Do not interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<!-- All other URLs are mapped to the node.js site entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="server.js"/>
</rule>
</rules>
</rewrite>
<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<!-- Make sure error responses are left untouched -->
<httpErrors existingResponse="PassThrough" />
<!--
You can control how Node is hosted within IIS using the following options:
* watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
* node_env: will be propagated to node as NODE_ENV environment variable
* debuggingEnabled - controls whether the built-in debugger is enabled
See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
-->
<!--<iisnode watchedFiles="web.config;*.js"/>-->
</system.webServer>
</configuration>

How can I use a service or global function in the #Component declaration?

I have a service that helps with some routing issues related to my environment and now I need to cope with template routing issues
Ideally I would be able to do something like this:
import { PathSvc } from './globals/path.svc';
#Component({
templateUrl: this.pathSvc.alias + '/app/my.comp.html'
})
export class MyComp {
constructor(private pathSvc: PathSvc) { }
}
Of course that doesn't work because the service isn't made to be used in the component declaration so I get a runtime console error:
requests:38 Error: TypeError: Cannot read property 'pathSvc' of
undefined(…)
Typescript and modular JS in genaral is a new thing to me. Maybe there's a more direct way to import my function to a bunch of modules and use it anywhere I want?
I realize this isn't an optimal approach and I should be getting these results using the
<base href="/{alias}" />
methodology but that's not happening right now.
The short of it is that somehow all that stopped working while I tried to make my ASP.Net MVC + Angular 2 app more dynamically aware of its hosting environment and handle back end and front end routing conflicts. I'd like to get this working in more of a "right way" but at the moment, I just need it to work so my boss is happy and a global routing script seems like the way.
if i correctly understood, you want to create a service that can act as an helper out of the angular's DI process.
Like #Günter_Zöchbauer said, an Injectable can't be used outward of an instance of an Ng2 object (Component, Injectable...) but in your case, create an injectable is irrelevant. Create an angular app doesn't mean that all your files must contain angular objects, you can also create basic classes/constants..
For example, if you want an helper that build an url for you, you can create a class and use it in a component declaration simply by importing it.
//urlbuilder.ts
const host:string = 'http://foo.bar.com';
export class UrlBuilder {
static generate(file:string = '') {
return host + file;
}
}
// component.ts
import {UrlBuilder} from './urlbuilder';
#Component({
templateUrl: UrlBuilder.generate('/foo.html')
})
export class FooComponent {}
You can even export a function instead of the class, it will work too.
The following code show the same code as above but with a function and an es6 tag.
//urlbuilder.ts
const host:string = 'http://foo.bar.com';
export const UrlBuilder = urlBuilderFn;
//basic tag function that build a template string with potential variables
function urlBuilderFn(strs, ...raws) {
let builtStr = '', length = strs.length;
for(let i = 0; i < length; i++)
builtStr = strs[i] + (raws[i] !== undefined && raws[i] !== null ? raws[i] : '');
return host + builtStr;
}
// component.ts
import {UrlBuilder as url} from './urlbuilder';
#Component({
templateUrl: url`/foo.html`
})
export class FooComponent {}
However, if wanna use your helper with the DI too (for whatever reasons, need for angular deps..) you can make an injectable class with a static method.
//urlbuilder.ts
const host:string = 'http://foo.bar.com';
#Injectable()
export class UrlBuilder {
constructor(private http:Http) {}
static generate(file:string = '') {
return host + file;
}
doSomething() {
return this.http.get(UrlBuilder.generate('/foo/bar'))
}
}
// component.ts
import {UrlBuilder} from './urlbuilder';
#Component({
templateUrl: UrlBuilder.generate('/foo.html')
})
export class FooComponent {
constructor(private builder:UrlBuilder) {}
ngOnInit() {
this.builder.doSomething()
}
}
On the other hand, if your only issues are with the templates why don't you write them directly as template string or just import them with your task manager (webpack..). Generally, if you don't have dynamic view and so, have to generete them from your server, prefer to avoid http calls to retrieve them and load them directly in the javascript files.
I've dealt with this issue and I solved it by making the baseHref a setting inside my configuration/appSettings. Make the Angular page container an .ASPX page, on page loads, set the baseHref from the configuration. Here is the snippets from my Web.config:
<appSettings>
<add key="AppBaseUrl" value="/MyAngularApp/" />
</appSettings>
And here are the re-write rules to make your Angular app function properly while being hosted in IIS:
<rewrite>
<rules>
<rule name="Rewrite Index to Root" stopProcessing="true" enabled="true">
<match url=".*" />
<conditions>
<add input="{URL}" pattern="^(.*)(/index.aspx)$" />
</conditions>
<action type="Redirect" url="{C:1}" />
</rule>
<rule name="Angular" stopProcessing="true" enabled="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.aspx/{R:0}" appendQueryString="true" />
</rule>
</rules>
</rewrite>

What's wrong with my connection string (can not access it through ConfiguratioManager)?

I'm trying to connect to Azure SQL, but I get error The underlying provider failed on Open
I discovered that I have put connection string in the wrong app.config - moved it to the executable project's app.config, but still the same result
When I check ConfigurationManager.ConnectionStrings["AzureDatabase"] it returns null. And when I try to connect it just uses default SQLExpress.
When I check build folder of my executable application - there is an <app_name>.exe.config file with "AzureDatabase". I'm stuck on where to search from here
This is my DbContext class
[DbConfigurationType(typeof(AzureDbConfiguration))]
public class ProductDbContext : DbContext
{
//Db sets
static MyContext()
{
//I don't want to change database from code
Database.SetInitializer<MyContext>(null);
}
public MyContext() : base("AzureDatabase") //this is my connectionstring
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//some mappings
}
}
This is AzureDbConfiguration
public class AzureDbConfiguration : DbConfiguration
{
public AzureDbConfiguration()
{
SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy(2, TimeSpan.FromSeconds(10)));
}
}
This is my app.config file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="AzureDatabase"
connectionString="Server=tcp:xxx.database.windows.net,1433;Initial Catalog=xxx;Persist Security Info=False;User ID=xxxx;Password=xxxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>
Any ideas on what's wrong here?
It turns out the problem was that I was trying to work with app.config from Class library (I was trying to create an integration tests)
One of potential solutions is to create a separate settings file for Class library. You can read about it in this StackOverflow post

Decompressing Web Api response

I am compressing web Api responses with following config
<system.webServer>
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
<scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
<dynamicTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="application/*" enabled="true" />
<add mimeType="*/*" enabled="false" />
</dynamicTypes>
<staticTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="application/*" enabled="true" />
<add mimeType="*/*" enabled="false" />
</staticTypes>
</httpCompression>
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
Now when i consume this in a Win Form applications and try to do the follwoing
var rawData = await response.Content.ReadAsStringAsync();
var deserializedData = JsonConvert.DeserializeObject<Employees[]>(rawData).ToList();
it fails on
var deserializedData = JsonConvert.DeserializeObject(rawData).ToList();
the Error message is
{"Unexpected character encountered while parsing value: . Path '', line 0, position 0."}
I guess this is due to the fact that content is gziped and not being deserialized. Can anyone suggest a solution ? This works locally fine as local IIS is not gzip enabled
You need to enable automatic GZip decompression:
var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip };
var client = new HttpClient(handler);

WCF + Silverlight + HttpContext.Current.Session is null

my problem....
i am tryingo to access session from Silverlight and WCF basicHttpBinding...
i saw some posts where it's possible (http://www.dotnetspider.com/Silverlight-Tutorial-317.aspx)
Mys cenario is:
Silvelright 4 FW 3.5
in web.config i have
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ViewModelDemo.Web.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ViewModelDemo.Web.Service1Behavior" name="ViewModelDemo.Web.Service1">
<endpoint address="" binding="basicHttpBinding" contract="ViewModelDemo.Web.Service1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
and my service:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Service1
{
[OperationContract]
publicvoid Test()
{
var session = System.Web.HttpContext.Current.Session;
}
}
and it''s call
var client = new Service1Client();
client.GetUserMacroFunctionsCompleted += new System.EventHandler<GetUserMacroFunctionsCompletedEventArgs>(client_GetUserMacroFunctionsCompleted);
client.GetUserMacroFunctionsAsync();
void client_GetUserMacroFunctionsCompleted(object sender, GetUserMacroFunctionsCompletedEventArgs e)
{
var test = ((Collection<Function>)e.Result);
}
HttpContext.Current is always null!
Any suggestions?
Yes HttpContext must be always null because your service configuration doesn't setup ASP.NET compatibility and your service doesn't require ASP.NET compatibility.
Add this to your configuration:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
And change AspNetCompatibilityRequirements so that your service cannot be hosted without former configuration:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
This link probably to help you.
http://blogs.msdn.com/b/sajay/archive/2006/08/03/687361.aspx
aspNetCompatibilityEnabled="true" doesn't help me till I set allowCookies="true" on the client binding configuration.
Update your web.config file to include
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
This should work, or else also change the AspNetCompatibilityRequirementsMode attribute on the contract to Required.

Resources