How to build your own portfolio website in .NET core and React

In this tutorial, we will build a personal portfolio website that will show case all of your professional skills. In this example, my website will contain software development skills, projects, previous jobs, etc. We will building a Single Page Application (SPA) in React.js and .NET Core. I recommend having some sort of programming knowledge, but it’s not required since all code will be provided. We will build each page and component step by step, then we will deploy to Microsoft Azure. I will keep this tutorial pretty straight forward so you can get this up and running. If you have any questions or feedback, please leave a comment

Click here to see what you will be building!

Click here if you would like to fork the project

Project Set up

  1. Download Visual Studio Community edition here.
  1. When you get to the Visual Studio Installer, Check ASP.NET Core Web Application and click Next. This could take awhile to install.
  1. Open Visual Studio and click Create a New Project. Select ASP.NET Core Web Application. Choose a Project name, and click Create.
  1. Choose the React.js template and click Create. 
  1. Once your project is created, select the run button at the top. This will take a few minutes since it has build all of the npm depedencies.
  1. Once it builds, you should get the following page in your browser! Let’s go over each folder to get an understanding.

Folder Structure Rundown

As mentioned before, this project will use React.js and .NET Core (C#). For those who don’t know, React is a JavaScript library created by Facebook and is used for building powerful UI components. React uses a programming language called JSX which stands for JavaScript XML. JSX allows you to write HTML in React. This will make more sense as we work on each page.
Let’s take a look at the folder structure and make some sense of it.

As you can see, the public directory contains index.html and manifest.json. You may have another file in there, you can just delete it if you want. Let’s do one thing, move index.js to your public directory.

index.html – Contains the root node HTML element. This is essentially a container of the entire application. ()

index.js – Renders the React Components BrowserRouter and App inside the HTML element “root”.

The src folder contains all of the JavaScript files that we will use. These JavaScript files are individual components of our application. You will get a better understanding later once we create our own.

The Controllers folder contains our Web API Controllers that handles HTTP requests to give data back to the front end of the application. We typically would retrieve data from a Database but in this case we will just retrieve from a JSON file. That way if you want to add more data such as Education or Jobs, you just update the JSON file.

The Pages folder contains cshtml pages, we will not be creating or touching these files.

The Program.cs and Startup.cs are required to run the application.

Now that we had an high level overview of the application, let’s move on to developing our personal resume! We will start with the About component.

Facade Design Pattern

 The Facade pattern is a Structural design pattern that provides a simple interface to hide the complex system underneath. It reduces dependencies to the client into a simplified interface. The client does not see what’s “under the hood” by reducing the dependencies into a simplified interface.

Facade makes APIs easier to use. Examples include a database connection API such as JDBC or a Redis caching service. You can create the underlying system to handle all of the logic that interacts with the database. Then you can have a Facade class that is called from client to Add, Update, or Select data from the database.

Let’s look at a simple implementation that allows the Client to view Upperbody and LowerBody workouts.

Here we are going to create a Workout interface with methodssetWorkout() and viewWorkout() and concrete classes LowerBody and UpperBody.  We then add the WorkoutFacade which interfaces the Workout implementations. The Client has no idea about the LowerBody and UpperBody classes. It just knows the two methods in WorkoutFacade, viewUpperBodyWorkout() and viewLowerBodyWorkout(). Let’s look at each class step by step.

  1. Create the Workout interface.
public interface Workout {

    void setWorkout();

    void viewWorkout();
}
  1. Create a simple Exercise class that contains an id and name. This is to store the exercises as an object.
public class Exercise {
    private int id;

    private String name;

    public Exercise(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

}
  1. Create the Concrete classes LowerBody and UpperBody that inherit Workout. In the setWorkout method we are adding hard coded exercises to a list. In viewWorkout(), we run an enhanced for loop and print out the Exercises names.

import java.util.ArrayList;
import java.util.List;

public class LowerBody implements Workout {

    private List<Exercise> exercises = new ArrayList<>();

    /**
     * Add lower body exercises to an ArrayList
     */
    @Override
    public void setWorkout() {
        exercises.add(new Exercise(1, "Squats"));
        exercises.add(new Exercise(2, "Calf raises"));
        exercises.add(new Exercise(3, "Lunges"));
    }

    /**
     * Loop through the list of exercises and print them out
     */
    @Override
    public void viewWorkout() {
        System.out.println("LowerBody Workout");
        for(Exercise exercise : exercises){
            System.out.println("Exercise: " + exercise.getName());
        }
    }
import java.util.ArrayList;
import java.util.List;

public class UpperBody implements Workout {

    private List<Exercise> exercises = new ArrayList<>();

    /**
     * Add upper body exercises to an ArrayList
     */
    @Override
    public void setWorkout() {
        exercises.add(new Exercise(1, "Bench Press"));
        exercises.add(new Exercise(2, "Push Ups"));
        exercises.add(new Exercise(3, "Inline Bench Press"));
    }

    /**
     * Loop through the list of Exercises and print them out
     */
    @Override
    public void viewWorkout() {
        System.out.println("UpperBody Workout");
        for(Exercise exercise : exercises){
            System.out.println("Exercise: " + exercise.getName());
        }
    }
}
  1. Create the WorkoutFacade class that will hide all of the logic from the Client. Here we create viewUpperBodyWorkout and viewLowerBodyWorkout that will call setWorkout and viewWorkout. The client does not access Workout, UpperBody, or LowerBody.
public class WorkoutFacade {

    /**
     * Creates an instance of UpperBody and calls setWorkout() and viewWorkout()
     */
    public void viewUpperBodyWorkout() {
        Workout workout = new UpperBody();
        workout.setWorkout();
        workout.viewWorkout();
    }

    /**
     * Creates an instance of LowerBody and calls setWorkout() and viewWorkout()
     */
    public void viewLowerBodyWorkout() {
        Workout workout = new LowerBody();
        workout.setWorkout();
        workout.viewWorkout();
    }
}
  1. Last but not least, we will create the WorkoutClient. We create an instance of WorkoutFacade and call viewUpperBodyWorkout() and viewLowerBodyWorkout(). This will produce the following output.
public class WorkoutClient {

    public static void main(String[] args){
        WorkoutFacade workoutFacade = new WorkoutFacade();

        workoutFacade.viewUpperBodyWorkout();
        System.out.println();
        workoutFacade.viewLowerBodyWorkout();
    }
}
UpperBody Workout
Exercise: Bench Press
Exercise: Push Ups
Exercise: Inline Bench Press

LowerBody Workout
Exercise: Squats
Exercise: Calf raises
Exercise: Lunges

Adapter Design Pattern

The Adapter pattern is a Structural design pattern that works as a bridge between multiple interfaces.

The Adapter Pattern will contain a class that will interface over another class that is not compatible in your application, and will make it compatible. The Adapter pattern is useful for connecting new code to a legacy application. For example, a new data source that contains similar data but different naming conventions and types. The Adapter class will return the data to the client the same way as our original data source.

Here’s another example that might be useful. Let’s say you want to connect an HDMI device to an old Box TV. Box TVs don’t have HDMI inputs, so you would need an adapter to convert HDMI to VGA. This is how this design pattern works. You have an application (TV) and a new class (HDMI) which can’t inherit from the same interface as RCA cables. You would build an Adapter class that interfaces over HDMI that can be used by TV in order to make this connection work.

Let’s look at another example in Java.

Here we have a Company interface that contains 4 methods to be implementedWe have a CompanyDB class that inherits Company. These are essentially Getters for id, name, description and foundedDate. Let’s say we get another data source to pull from in our application CompanyOtherDB (Not a recommended class name) . This contains similar data but different names and types. We can’t directly inherit Company from this class because there are different names and types. The Adapter pattern will take care of this for us. Let’s look at each class step by step.

  1. Create the Company Interface.
public interface Company {
    int getId();
    String getName();
    String getDescription();
    String getFoundedDate();
}
  1. Create the CompanyDB class that inherits Company. Add the private variables for id, name, description and foundDate. Add the constructor as well.
public class CompanyDB implements Company {

    private int id;
    private String name;
    private String description;
    private String foundedDate;

    public CompanyDB(int id, String name, String description, String foundedDate) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.foundedDate = foundedDate;
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public String getFoundedDate() {
        return foundedDate;
    }
}
  1. Create the class CompanyOtherDB. This class acts as an interface to other data source we mentioned above. I’m sure when you look at this class, there are ways to make this class inherit Company, but that’s not the point of the pattern. The goal is here to not change any legacy code, because remember, we are adding this class after the fact.
public class CompanyOtherDB {

    private String cid;
    private String companyName;
    private String companyInformation;
    private String dateFounded;

    public CompanyOtherDB(String cid, String companyName, String companyInformation, String dateFounded) {
        this.cid = cid;
        this.companyName = companyName;
        this.companyInformation = companyInformation;
        this.dateFounded = dateFounded;
    }

    public String getCid() {
        return cid;
    }

    public String getCompanyName() {
        return companyName;
    }

    public String getCompanyInformation() {
        return companyInformation;
    }

    public String getDateFounded() {
        return dateFounded;
    }
}
  1. Create CompanyAdapter that inherits Company. In this class, we create a private instance of CompanyOtherDB and set this in the Constructor. In the override methods, we return the properties from CompanyOtherDB as if they work with Company. 
/**
 * Inherits Company
 */
public class CompanyAdapter implements Company {

    //Instance of CompanyOtherDB
    private CompanyOtherDB companyOtherDB;

    //Takes an instance as parameter
    public CompanyAdapter(CompanyOtherDB companyOtherDB) {
        this.companyOtherDB = companyOtherDB;
    }

    /**
     * Returns the cid as an integer instead of a string
     *
     * @return
     */
    @Override
    public int getId() {
        return Integer.parseInt(companyOtherDB.getCid());
    }

    /**
     * Returns companyName as Company.name
     * @return
     */
    @Override
    public String getName() {
        return companyOtherDB.getCompanyName();
    }

    /**
     * Returns companyInformation as Company.description
     * @return
     */
    @Override
    public String getDescription() {
        return companyOtherDB.getCompanyInformation();
    }

    /**
     * Returns dateFounded as Company.foundedDate
     * @return
     */
    @Override
    public String getFoundedDate() {
        return companyOtherDB.getDateFounded();
    }
}
  1. In the CompanyClient, we create an instance of Company and CompanyOtherDB. We pass companyOtherDB into CompanyAdapter and it will return us an instance of Company. That is how the Adapter Design Pattern works.
public class CompanyClient {

    public static void main(String[] args){
        //Instance of company
        Company company = new CompanyDB(1, "Amazon", "Online shopping", "1994");

        //Instance of companyOtherDB
        CompanyOtherDB companyOtherDB = new CompanyOtherDB("2", "Best Buy", "Electronics Retailer", "1966");

        //This won't work!
        //Company company2 = new CompanyOtherDB("3", "Facebook", "Social Media and Technology", "2004");
        //Error: Incompatible Types

        Company newCompany = new CompanyAdapter(companyOtherDB);

        System.out.println("Company Id from Original DB: " + company.getId());
        System.out.println("Company Name from Original DB: " + company.getName());
        System.out.println("Company Description from Original DB: " + company.getDescription());
        System.out.println("Company Founded Date from Original DB: " + company.getFoundedDate());
        System.out.println();
        System.out.println("Company Id from New DB: " + newCompany.getId());
        System.out.println("Company Name from New DB: " + newCompany.getName());
        System.out.println("Company Description from New DB: " + newCompany.getDescription());
        System.out.println("Company Founded Date from New DB: " + newCompany.getFoundedDate());
    }
}
Company Id from Original DB: 1
Company Name from Original DB: Amazon
Company Description from Original DB: Online shopping
Company Founded Date from Original DB: 1994

Company Id from New DB: 2
Company Name from New DB: Best Buy
Company Description from New DB: Electronics Retailer
Company Founded Date from New DB: 1966