Blazor WebAssembly Hosted on IIS - only API is working - c#

I have a project where I have developed a solution with Blazor WebAssembly Hosted model. I have a Server Project, a Client project and a Shared project (but I renamed it to Domain).
When I publish, I publish to a folder because I have no other ways of connecting to the server I try to deploy to, which is of Windows Server 2019 OS.
Then I add the published folder to IIS but I can only get the API endpoints working. When I navigate to the root of the application, i.e. https:/localhost:5001 I just get a 404 error.
I have installed dependencies for .NET, URL Rewrite, added a self-signed certificate etc.
I can't understand why this doesn't work. When I launch in Visual Studio it works perfectly fine both with Release and Debug flags.
Can someone guide me to what I might have done wrong?
Log when navigating to https://localhost:5001 (not working)
[10:11:24 Information] Microsoft.Hosting.Lifetime Application started.
Press Ctrl+C to shut down.
[10:11:24 Information] Microsoft.Hosting.Lifetime Hosting environment:
Production
[10:11:24 Information] Microsoft.Hosting.Lifetime Content root path:
C:\Temp\Project\publish
[10:11:24 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/2 GET https://localhost:5001/ - -
[10:11:25 Information] Microsoft.AspNetCore.Routing.EndpointMiddleware
Executing endpoint 'Fallback {*path:nonfile}'
[10:11:25 Information] Microsoft.AspNetCore.Routing.EndpointMiddleware
Executed endpoint 'Fallback {*path:nonfile}'
[10:11:25 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request finished HTTP/2 GET https://localhost:5001/ - - - 404 - -
94.7963ms
Log when navigating to https://localhost:5001/api/dataflows (works)
[10:12:46 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/2 GET https://localhost:5001/api/dataflows - -
[10:12:47 Information] Microsoft.AspNetCore.Routing.EndpointMiddleware
Executed endpoint
'Project.Web.Server.Controllers.DataflowsController.GetDataflows
(Project.Web.Server)'
[10:12:47 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request finished HTTP/2 GET https://localhost:5001/api/dataflows - - -
200 2 application/json;+charset=utf-8 619.7714ms
Project.Web.Server/Statup.cs: ConfigureServices
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("{*path:nonfile}", "index.html"); ;
});
}
Project.Web.Server/appsettings.json
{
"AllowedHosts": "*",
"ConnectionStrings": {
"Identity": "Data Source=|DataDirectory|\\IdentityDB.db",
"Api": "Data Source=|DataDirectory|\\ApiDB.db",
"Dataflows": "Data Source=|DataDirectory|\\DataflowsDB.db"
},
"IdentityServer": {
"Clients": {
"Project.Web.Client": {
"Profile": "IdentityServerSPA"
}
},
"Key": {
"Type": "Store",
"StoreName": "My",
"StoreLocation": "CurrentUser",
"Name": "CN=localhost"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "C:\\Temp\\Project\\log.txt",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
}
}
],
"Enrich": [ "FromLogContext", "WithExceptionDetails" ]
}
}
Project.Web.Client/Program.cs: ConfigureServices
public static Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("Project.Web.Client.API", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Project.Web.Client.API"));
// Add Open ID Connect client settings
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
}).AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
builder.Services.AddApiAuthorization();
return builder.Build().RunAsync();
}
Project.Web.Client/Shared/RedirectToLogin.razor
#inject NavigationManager Navigation
#code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
}
Project.Web.Client/wwwroot/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Project.Web</title>
<base href="/" />
<link href="/company/fonts/css/styles-technical.css" rel="stylesheet" />
<link href="/company/icons/css/styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">Loading...</div>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script type="module">
import { defineCustomElements } from '/company/web-ui/loader/index.js';
defineCustomElements(window);
</script>
<script src="/scripts/utils.js"></script>
</body>
</html>
Project.Web.Client/wwwroot/appsettings.json
{
"oidc": {
"Authority": "https://localhost:5001/",
"ClientId": "Project.Web.Client",
"ResponseType": "code",
"DefaultScopes": [
"openid",
"profile"
],
"PostLogoutRedirectUri": "/",
"RedirectUri": "https://localhost:5001/authentication/login-callback"
}
}

Make sure you install the .Net core hosting bundle on your server.
https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-6.0.3-windows-hosting-bundle-installer

Related

AWS Gateway subroutes support for Asp.Net Core

I have created a simple AWS Serverless "Asp.Net Core minimal" application and everything works as expected because the application(lambda) is bound to the root route of the Api gateway.
Here is the configuration:
"Events": {
"ProxyResource": {
"Type": "Api",
"Properties": {
"Path": "/{proxy+}",
"Method": "ANY"
}
},
"RootResource": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "ANY"
}
}
}
But if to change the root route to some subroute, for example "/hello",
"Events": {
"ProxyResource": {
"Type": "Api",
"Properties": {
"Path": "/hello/{proxy+}",
"Method": "ANY"
}
},
"RootResource": {
"Type": "Api",
"Properties": {
"Path": "/hello/",
"Method": "ANY"
}
}
}
everything stops working and the application constantly returns a 404 error instead of a welcome message.
Here is the application code:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAWSLambdaHosting(LambdaEventSource.RestApi);
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.MapControllers();
app.MapGet("/", () => "Welcome to running ASP.NET Core Minimal API on AWS Lambda");
app.Run();
Is it possible somehow to explain for the application that now it is bound to the subroute?
you need to change the url in function. It needs to be like:
app.MapGet("/hello", () => "Welcome to running ASP.NET Core Minimal API on AWS Lambda");
And if you have any route parameters you can also use
app.MapGet("/hello/{name}", (string name) => "Welcome to running ASP.NET Core Minimal API on AWS Lambda");

How to setup a React app on a branch in .Net Core web app?

I created a .Net Core Web app with React via a predefined template on visual studio 2019. The generated app works and runs the reactjs app at the root address.
https://localhost:44349/
Now I want to run this reactjs app on the branch address leaving me with the option of using server-side views and routes for the remaining addresses.
https://localhost:44349/app
To achieve this, I found the following code from the following website and another question.
https://www.strathweb.com/2017/04/running-multiple-independent-asp-net-core-pipelines-side-by-side-in-the-same-application/
https://stackoverflow.com/a/60225489
Following the approach suggested above, I was able to access my reactjs app via /app.
My issue is that now on running this app in development mode, it doesn't work as all the requests to /app/* returns the index page of the reactjs app, including for static resources such as js/css etc, resulting in the following errors in web console for js files
Uncaught SyntaxError: expected expression, got '<'
Startup.cs, Configure
const string spaPath = "/app";
app.Map(new PathString(spaPath), appMember =>
{
string clientAppPath = env.IsDevelopment() ? "ClientApp" : #"ClientApp/build";
StaticFileOptions clientAppDist = new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), clientAppPath)
)
};
appMember.UseStaticFiles(clientAppDist);
if (!env.IsDevelopment())
{
appMember.UseSpaStaticFiles(clientAppDist);
}
appMember.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
package.json
{
"name": "react_app",
"version": "0.1.0",
"private": true,
"homepage": "app",
"dependencies": {
"bootstrap": "^4.1.3",
"jquery": "^3.5.1",
"merge": "^1.2.1",
"oidc-client": "^1.9.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.4",
"reactstrap": "^8.4.1",
"rimraf": "^2.6.2"
},
"devDependencies": {
"ajv": "^6.9.1",
"cross-env": "^5.2.0",
"typescript": "^3.7.5",
"eslint": "^6.8.0",
"eslint-config-react-app": "^5.2.0",
"eslint-plugin-flowtype": "^4.6.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.18.3",
"nan": "^2.14.1"
},
"eslintConfig": {
"extends": "react-app"
},
"scripts": {
"start": "rimraf ./build && react-scripts start",
"build": "react-scripts build",
"test": "cross-env CI=true react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint ./src/"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
I have already tried solutions applicable to Angular but they don't seem to work, maybe due to missing wwwroot.

"Authorization failed. AuthenticationScheme: AzureADJwtBearer was challenged" in ASP.NET Core 3.1 Web API - 401 Unauthorized

I'm trying to authenticate my web api using Azure AD.
I'm following this tutorial and I successfully authenticated using my Angular App.
The problem is, when I put the Authorize attribute in my controller, it gives me 401 Unauthorized error in my angular console and even my post man.
As I view my web api log, it shows like this:
Image here
Here's my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// removed because this doesn't work either
// services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
// .AddAzureADBearer(options => Configuration.Bind("AzureActiveDirectory", options));
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureActiveDirectory", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
// This is a Microsoft identity platform web API.
options.Authority += "/v2.0";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
}
}
In my appsettings.json:
"AzureActiveDirectory": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "myorg.onmicrosoft.com",
"TenantId": "241234-12ad-1234-1234-123412341234", // sample only
"ClientId": "8786687-12ad-1234-1234-2341432341" // sample only client id from the webapi in the ad
},
In my App Client, here's my app.module.ts
// MSAL Imports
import {
MsalModule,
MsalInterceptor,
MSAL_CONFIG,
MSAL_CONFIG_ANGULAR,
MsalService,
MsalAngularConfiguration
} from '#azure/msal-angular';
import { Configuration } from 'msal';
// MSAL Configs
export const protectedResourceMap:[string, string[]][]=[['https://localhost:5000/', ['api://WEB-API-CLIENTID/api-access']] ];
const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;
function MSALAngularConfigFactory(): MsalAngularConfiguration {
return {
popUp: !isIE,
consentScopes: [
"user.read",
"openid",
"profile",
"api://WEBAPI-CLIENT-ID/api-access"
],
unprotectedResources: ["https://www.microsoft.com/en-us/"],
protectedResourceMap,
extraQueryParameters: {}
};
}
function MSALConfigFactory(): Configuration {
return {
auth: {
clientId: 'ANGULAR-CLIENT-ID',
authority: "https://login.microsoftonline.com/TENANT-ID", /// with tenant id
validateAuthority: true,
redirectUri: "http://localhost:4200/",
postLogoutRedirectUri: "http://localhost:4200/",
navigateToLoginRequestUrl: true,
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: isIE, // set to true for IE 11
},
};
}
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
RouterModule.forRoot(appRoutes),
NgHttpLoaderModule.forRoot(),
FormsModule,
// msal angular
MsalModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_CONFIG,
useFactory: MSALConfigFactory
},
{
provide: MSAL_CONFIG_ANGULAR,
useFactory: MSALAngularConfigFactory
},
MsalService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Other info: I already saw this thread but it doesn't help fix my issue.
I look forward for someone's help.
BearerAuthenticationScheme The default scheme for Azure Active Directory B2C Bearer. If you are using, AddAzureADB2CBearer(AuthenticationBuilder, Action<AzureADB2COptions>) then use JwtBearerAuthenticationScheme otherwise, use the default bearer scheme.
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureActiveDirectory", options));
Please double check your clientId if it's assigned properly to your app. It's only normal to throw a 401 unauthorized if it's doesn't meet its security requirements.
If you can get the accessToken, try if it's a valid access token. Visit this jwt.ms. It should work using Postman if it is valid. make sure you use Authorization Bearer your_accessToken_here.
You should expect a 200 Ok in postman. Now, try it on your angular app.

Serilog setup for Asp.Net Core 2.0.1 not producing logs

I am trying to set up Serilog as my logger in a multi project solution using Asp.Net Core 2.0.1, EF 2.0.1 and MVC 6.
I have set up Serilog, mostly following the guidelines from this blog post
Set up Serilog post
There is a problem with the json in that post, which I've corrected and is shown here
appsettings.json file
{
"ApplicationConfiguration": {
"ConnectionStrings": {
"DevelopmentConnection": "Server=(localdb)\\mssqllocaldb;Database=TestingConfigurationNetCoreTwo_Development;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"ApplicationInfo": {
"VersionNumber": "1.0.0",
"Author": "Jimbo",
"ApplicationName": "CustomTemplate",
"CreatedOn": "November 20, 2017"
},
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile",
"Serilog.Sinks.Async",
"Serilog.Sinks.ApplicationInsights",
"Serilog.Sinks.Console",
"Serilog.Sinks.Seq"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "RollingFile",
"Args": { "pathFormat": "Logs/log-{Date}.log" }
}
]
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
"Properties": {
"Application": "CustomTemplate"
}
}
}
}
I have the logger configuration work in the Main method of Program.cs
public static int Main(string[] args)
{
var currentEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{currentEnv}.json", optional: true)
.AddEnvironmentVariables()
.Build();
//Configure logger
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
//do this for now
//TODO:figure out how to add to Serilog config in appsettings.json
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
Log.Information("Logger created");
try
{
Log.Information("Starting web host");
BuildWebHost(args).Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Web Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
//BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
And in my Startup method
services.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true));
I was assuming Serilog would create the Logs folder once I ran the application, but no Log folder or logs. I added a Logs folder to both the root of the solution, and the web app project and ran it again. No logs. The blog post author suggested looking in the machines root folder. I looked both in C and OneDrive main folders (Solution Files stored on OneDrive) but there were no logs there either.
I then noticed a stack trace warning
System.IO.FileNotFoundException occurred
HResult=0x80070002
Message=The configuration file 'appsettings.json' was not found and is not optional. The physical path is 'C:\Users\OneDrive\TestingConfigurationAspNetCore2\TestMvcApp\bin\Debug\netcoreapp2.0\appsettings.json'.
Source=
StackTrace:
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
I was getting this warning before, which is why I added the set bath path method in the configuration set up, because the application was failing at the code line
.AddJsonFile("appsettings.json")
which was fairly obvious because in the bin folder the file isn't called appsettings.json, its called TestMvcApp.runtimeconfig.json.
Once I added .SetBasePath, the code ran through so I put a breakpoint after the configuration setup. The configuration object contains all the settings from my appsettings.json file, so why I am getting that stack trace error? I don't know but I'll bet a dollar it is why there are no logs (no log messages appear in the console either)
Any help on why the stack trace error or why there are no log messages being written to the log file or displayed on the console would be most appreciated.
which was fairly obvious because in the bin folder the file isn't
called appsettings.json, its called TestMvcApp.runtimeconfig.json.
TestMvcApp.runtimeconfig.json is not your appsettings.json, it's a runtime configuration file as it's clear from the file name.
I bet that your appsettings.json is just not copied to the output directory during the build. To fix that select appsettings.json in Visual Studio Solution explorer, in context menu select properties and set 'Copy to Output Directory' to 'Copy always' or 'Copy if newer':
Also your json configuration has several issues:
Serilog section should not be inside ApplicationConfiguration section. It should be on the same level, i.e top-level section.
You also has suspicious WriteTo section with one sink (RollingFile) inside another (Async). Check here sample json configuration for Serilog.
In order to use all those sinks (Serilog.Sinks.RollingFile, Serilog.Sinks.Console) you should install corresponding sink NuGets: Serilog.Sinks.RollingFile, Serilog.Sinks.Console, etc.
Here is configuration file (with RollingFile sink only) that works fine for me:
{
"ApplicationConfiguration": {
"ConnectionStrings": {
"DevelopmentConnection": "Server=(localdb)\\mssqllocaldb;Database=TestingConfigurationNetCoreTwo_Development;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"ApplicationInfo": {
"VersionNumber": "1.0.0",
"Author": "Jimbo",
"ApplicationName": "CustomTemplate",
"CreatedOn": "November 20, 2017"
}
},
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "RollingFile",
"Args": { "pathFormat": "c:\\log-{Date}.log" }
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "CustomTemplate"
}
}
}
The accepted answer only logged some messages for my project. I'm not sure why. Ondrej Balas's approach worked better for me.
Install these Nuget packages:
Serilog
Serilog.AspNetCore
Serilog.Settings.Configuration
Serilog.Sinks.Console
Serilog.Sinks.RollingFile
Program.cs needs .UseSerilog() added:
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
})
.UseStartup<Startup>()
.UseSerilog()
.Build();
Startup.cs needs a couple of changes
one in the constructor:
public Startup(IConfiguration configuration)
{
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
Configuration = configuration;
}
another in Configure():
loggerFactory.AddSerilog();
if (env.IsDevelopment())
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
}
appsettings.json needs a Serilog config entry in root:
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "RollingFile",
"Args": {
"pathFormat": "logs\\log-{Date}.log",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] {Message}{NewLine}{Exception}"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "My Application"
}
}

ASP.Net Core: Setting up React.js + webpack + babel in VS2017

After some playing around with ReactJS (which was running on webpack-server), decided to try ASP.Net Core project in Visual Studio 2017.
I created empty ASP.Net Core project.
Removed all stuff in
wwwroot and stuff related to bower.
Removed all controllers
except Home and removed views except Home/Index.cshtml
Added
webpack.config.js, package.json and .babelrc
Installed Asp.React from Nuget
My package:
{
"name": "TrainDiary",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"css-loader": "^0.28.4",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"style-loader": "^0.18.2"
},
"devDependencies": {
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.4",
"html-webpack-plugin": "^2.29.0",
"clean-webpack-plugin": "^0.1.16",
"style-loader": "^0.18.2",
"webpack": "^3.4.1",
"webpack-dev-server": "^2.6.1"
},
"-vs-binding": {
"BeforeBuild": [
"build"
]
}
}
My webpack config:
var CleanWebpackPlugin = require('clean-webpack-plugin');
let path = require('path');
const bundleFolder = "wwwroot/build/";
var HTMLWebpackPlugin = require('html-webpack-plugin');
var HTMLWebpackPluginConfig = new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'Scripts/app/index.html'),
filename: 'index.html',
inject: 'body'
});
module.exports = {
entry: path.resolve(__dirname, 'Scripts/app/Core/app.js'),
module:{
loaders:[
{
test: /\.js$/,
exclude: [/node_modules/],
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
}
]
},
output:{
filename: 'index.js',
path: path.resolve(__dirname, bundleFolder)
},
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
},
plugins: [ new CleanWebpackPlugin([bundleFolder]), HTMLWebpackPluginConfig]
};
babelrc is simple like that { presets:['react'] }
So, when I run npm run build everything is fine, in wwwroot it generates index.js and index.html as well.
But nothing happens when I run my application! I mean nothing at all. Blank white page. No error in console. Just like that.
Also, here is my Startup.cs:
namespace TrainDiary.Web
{
using React.AspNet;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddLogging();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddReact();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseReact(config =>
{ });
app.UseStaticFiles();
app.UseDefaultFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
HomeController:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
Home/Index.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello world</title>
</head>
<body>
<!-- Load all required scripts (React + the site's scripts) -->
#Html.Partial("~/wwwroot/build/index.html")
</body>
</html>
How come? What's wrong with this approach?
UPD:
entrance point index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/Core/app.js'
ReactDOM.render(
<App />,
document.getElementById("content")
);
Core/app.js is here
import React from 'react';
import ReactDOM from 'react-dom';
export default class App extends React.Component {
render() {
return (
<div>
Hello, React!
</div>
)
}
}
index.html which we try to render in Index.cshtml
<body>
<div id='content'></div>
</body>
Screenshot of the rendered stuff:
UPD2:
As Jose advised - changed Index.cshtml like that:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello world</title>
<script src="~/build/index.js"></script>
</head>
<body>
<!-- Load all required scripts (React + the site's scripts) -->
<div id='content'></div>
</body>
</html>
and removed webpack-html-plugin.
Thats what I got now (css-loader worked btw):
UPD3:
Fixed some wrong stuff in webpack.config (entry point), but still no success:
var CleanWebpackPlugin = require('clean-webpack-plugin');
let path = require('path');
const bundleFolder = "wwwroot/build/";
module.exports = {
entry:'./Scripts/index.js',
module:{
loaders:[
{
test: /\.js$/,
exclude: [/node_modules/],
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
}
]
},
output:{
filename: 'index.js',
path: path.resolve(__dirname, bundleFolder)
},
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
},
plugins: [ new CleanWebpackPlugin([bundleFolder])]
};
UPD4:
Now it works! In previous case I just didn't pay attention to debugger console with error like described here.
So I changed Index.cshtml like that:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello world</title>
</head>
<body>
<!-- Load all required scripts (React + the site's scripts) -->
<div id='content'></div>
<script src="~/build/index.js"></script>
</body>
</html>
And now it's fine!
Summary of comments' suggestions:
Make sure is being render in your app code
Remove HTMLWebpackPlugin and change Index.cshtml to include bundle.js ( the partial view in your mvc view is loading a full template by using html wepack plugin)
Glad to help you!

Categories