First, we will create an ASP.NET Core application, then we will set up Webpack and Babel. At the end, we will create a simple react-redux project.
Content
- Prerequisites
- Create ASP.NET Core MVC Application
- Setting up Webpack and Babel
- Add ReactJS to Project
- Run Project
- Adding Hot Module Replacement To Project
- Adding Webpack middleware
- Adding hot module replacement
- Create Required React Components and navigate between them
- Adding simplified Version of Components
- Add Navigation Between Components
- Server-side vs Client-side Routing
Prerequisites
Before we start, you’ll need to make sure you have the following installed pc (or mac).
- Node JS
- Visual Studio Code
- NET Core SDK (I’m using SDK version 2.2)
- Omni Sharp C# extension
If we have everything set up, let’s start by creating a new ASP.NET Core Web Application.
Create ASP.NET Core MVC Application
We’re going to create a new project from Command Prompt using the command dotnet new mvc of ASP.NET Core CLI.
First, open a Command Prompt window and navigate to the desired folder then run the below commands to create a new project.
mkdir MyApp cd MyApp dotnet new mvc
Run the below command to open a newly created project in Visual Studio Code.
Code .
If you already installed C# extensions add-on it will prompt you to create required assets for this project, press “yes”.
To start working with npm packages for this project run below command in terminal.
npm init
It will ask you a few questions, you may leave them blank if you don’t know what to fill.
After finishing this command, packages.json file will be added to the project’s root directory.
Setting up Webpack and Babel
Webpack is an open-source JavaScript module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resources or assets. Webpack takes modules with dependencies and generates static assets representing those modules.
Since we want to use React.js and it is a JavaScript library, we need Webpack to bundle our JavaScript code for browsers.
If you didn’t already install Webpack globally run below:
npm install webpack –g npm install webpack-cli -g
Then run below to add Webpack to your project:
npm i webpack –-save-dev npm i webpack-cli --save-dev
open package.json file and add below
{ "name": "myapp", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^4.20.2", "webpack-cli": "^3.1.2" } }
We will write our React app with ES6. ES6 refers to version 6 of the ECMA Script programming language. ECMA Script is the standardized name for JavaScript, and version 6 is the next version after version 5, which was released in 2011. Not all Browsers support es6, so we need a translator for this purpose.
Babel is a JavaScript compiler that includes the ability to compile JSX into regular JavaScript.
Run below commands in CMD to add required Babel libraries to our project:
npm install babel-loader @babel/core --save-dev npm install @babel/preset-env --save-dev npm install @babel/preset-react --save-dev
Create .babelrc configuration file in the root folder of the project with below content.
{ "presets": ["@babel/preset-env", "@babel/preset-react"] }
Now, we should set up Webpack to use Babel as loader of JavaScript files.
Create webpack.config.js file in root directory of the project and write below code in it.
const path = require('path'); module.exports = { entry: "./src/index.js", mode: "development", // "production" | "development" | "none" output: { path: path.resolve(__dirname, "./wwwroot/dist"), // string // the target directory for all output files // must be an absolute path (use the Node.js path module) filename: "bundle.js", // the filename template for entry chunks publicPath: "dist/", // string // the url to the output directory resolved relative to the HTML page }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader" } } ] } }
Add ReactJs to Project
Run the below command to add React to our project.
npm install react react-dom --save-dev
Create src folder in the root directory of the project then create src/index.js file.
import * as ReactDOM from 'react-dom'; import * as React from 'react'; ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
Edit Views/Home/Index.cshtml file and remove extra content from it to be like below.
@{ ViewData["Title"] = "Home Page"; } <div id="root">Loading...</div> @section scripts { <script src="~/dist/bundle.js"></script> }
Now, that we added React to our project we don’t need other razor views and controllers came with ASP.NET Core CLI Template. So let get rid of them.
First, lets clear wwwroot folder completely then delete extra views like Privacy.cshtml file from View folder. We only need Index.cshtml file. Then modify layout.cshtml file as below:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - MyApp</title> <base href="~/" /> </head> <body> @RenderBody() @RenderSection("scripts", required: false) </body> </html>
Rename Models folder to ViewModels. Then open ErroViewModel file and change the namespace of it.
Open ViewImports.cshtml and Change namespace there
@using MyApp.ViewModels
And also change using of this namespace in HomeController.cs file.
Run Project
Before testing our application first run below command to create our bundle.js file
Webpack --config webpack.config.js
Then run
dotnet run
you may see below Privacy error:
DOTNET 2.2 templates by default require HTTPS for all requests and redirect all HTTP requests to HTTPS. When we run newly created project, we will see serving pages as below:
.../myapp>dotnet run Hosting environment: Development Content root path: C:\Code\imnapo\naravan Now listening on: https://localhost:5001 Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
As you can see, its listening HTTP over port 5000 and HTTPS over port 5001. If we run the application, we may see an above privacy error.
To solve this untrusted SSL cert error on local machine and to trust the certificate run below command:
dotnet dev-certs https --trust
Below message will be shown, press Yes to install the certificate.
You may close your browser and open it again to see trusted badge in your browser. Now you should see “Hello World!” text in your browser.
Now try to change Hello World text and save the file and refresh your browser, but as you can see nothing happened. This happens because we didn’t run our Webpack command to bundle our JavaScript again. There is a solution for this to instantly see changes as you save your file and we will explain it in the next topic.
Adding Hot Module Replacement
What we need are Webpack middleware and Hot Module replacement:
Webpack middleware so that, during development, any webpack-built resources will be generated on demand, without you having to run webpack manually or compile files to disk
Hot module replacement so that, during development, your code and markup changes will be pushed to your browser and updated in the running application automatically, without even needing to reload the page.
Adding Webpack middleware
Open Startup.cs file and add this using:
using Microsoft.AspNetCore.SpaServices.Webpack;
Go to configure method and add Webpack middleware to it as below:
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true, ReactHotModuleReplacement = true }); } else { app.UseExceptionHandler("/Home/Error"); }
Open _ViewImports.cshtml file and add below to it:
@addTagHelper *, Microsoft.AspNetCore.SpaServices
there are other packages to install too:
npm install react-hot-loader npm install --save-dev webpack-hot-middleware npm install --save-dev webpack-dev-middleware
Adding hot module replacement:
We need two packages to enable hot module replacement in ASP.NET, so run below command:
npm install -D aspnet-webpack aspnet-webpack-react
our webpack configuration file needs some modifications:
- Change entry point from string of “./src/index.js” to an object with a main property like
{ main: "./src/index.js"}
.
- Change entry point from string of “./src/index.js” to an object with a main property like
- Add react-hot-module/babel plugin to babel-loader
So open webpack.config.js file and change it as below:
const path = require('path'); module.exports = { entry: { main: "./src/index.js"}, mode: "development", // "production" | "development" | "none" output: { path: path.resolve(__dirname, "./wwwroot/dist"), // string // the target directory for all output files // must be an absolute path (use the Node.js path module) filename: "bundle.js", // the filename template for entry chunks publicPath: "dist/", // string // the url to the output directory resolved relative to the HTML page }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { plugins: [ "react-hot-loader/babel" ] } } } ] } }
Add the end open index.js and add this to end of the file.
// Allow Hot Module Replacement if (module.hot) { module.hot.accept(); }
Now, run the project and try to change the “hello world!” Text one more time and save the file. You should instantly see changes in your browser (you may run webpack –config webpack.config.js command ones more before run the app).
Create Required React Components and navigate between them
Now, that we initialized our ASP.NET Application, let create our React app to register and login users. To simplify, we are going to create 4 components. We are not going to define the real implementation of them. We will just return simple text in render method of each component.
- App.js : our main component (Home Page)
- Signin.js : our login page
- Signup.js: our register page
- Feature.js: our special component that should be shown only when a user is logged in.
Add body of Required Components
We are going to create simple components first and not their real implementation. let us create “components” folder under “src” folder:
…\src> mkdir components
Then let’s create our first component named app.js:
…\src>cd components …\src\components> echo.>App.js
Modify app.js file as below:
import React, { Component} from "react"; class App extends Component { constructor(props) { super(props); } render() { return (<div>This is App.js</div>); } } export default App;
Then let’s create the Feature component:
…\src\components> echo.> Feature.js
Modify the Feature.js file as below:
import React, { Component} from "react"; class Feature extends Component { constructor(props) { super(props); } render() { return ( <div>This is Feature.js. Only authenticated Users allowed.</div> ); } } export default Feature;
For the sake of having a better structure, we will create a sub-folder for related components.
Create a new folder named auth, under the components folder and add Signup.js and Signin.js files to it.
…\src\components> mkdir auth …\src\components\auth> echo.> Signin.js …\src\components\auth> echo.> Signin.js
Create the sign-in component as below:
import React, { Component } from 'react'; class Signin extends Component { constructor(props) { super(props); } render() { return("This is signin page!") } } export default Signin;
Next, let’s create our signup component:
import React, { Component } from 'react'; class Signup extends Component { constructor(props) { super(props); } render() { return("This is signup page!") } } export default Signup;
Add Navigation Between Components
Next step is to create navigation between these 4 components.
Add react router to project.
npm install react-router-dom
modify the index.js file to add the ability to navigate between components.
import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { BrowserRouter as Router, Route, Link,Switch } from "react-router-dom"; import App from './components/App'; import Signup from './components/auth/Signup'; import Signin from './components/auth/Signin'; ReactDOM.render( <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/Signin">Signin</Link> </li> <li> <Link to="/Signup">Signup</Link> </li> </ul> <hr /> <Route exact path="/" component={App} /> <Route path="/Signin" component={Signin} /> <Route path="/Signup" component={Signup} /> </div> </Router> , document.getElementById('root') ); // Allow Hot Module Replacement if (module.hot) { module.hot.accept(); }
Run the application and if everything is set as I said you should be able to navigate between these components using provided links inside home page. Now, try to type desired page (Ex. http://localhost:5000/Signin) on the browsers address bar. As you can see, it will not load the page. So why we access our pages from links inside pages but we can not access them when typing directly on browsers address bar?
Server-side vs Client-side Routing
The first big thing to understand about this is that there are now 2 places where the URL is interpreted. The very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router etc. Only when those scripts have loaded does phase 2 start.
So in our case when we type directly on browsers address bar no react-router is running yet. So, it will make a server request and that is where our problem starts.
To fix this we should add single page application fallback rout to our ASP.NET application routes. Open startup.cs file and modify below section in Configure method:
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); routes.MapSpaFallbackRoute( name: "spa-fallback", defaults: new { controller = "Home", action = "Index" }); });
Run the application once again and now if we type our desired page like http://localhost:5000/Signin on browser’s address bar, we will navigate to the sign in page.
In the next chapter, we will create an Authentication Controller using OpenIddict package. We will also create an Accounts controller to register and manage users.