Build Secure Microservices with AWS Lambda and ASP.NET Core

Build Secure Microservices with AWS Lambda and ASP.NET Core

Microservices are fun to build and offer us a scalable path to overcoming problems with tightly coupled dependencies that plague monolithic applications.

Microservices are fun to build and offer us a scalable path to overcoming problems with tightly coupled dependencies that plague monolithic applications.

This post will walk you through building an AWS Lambda microservice written in C# with .NET Core 2.1, and communicating in JSON. We’re bringing together multiple exciting technologies here - microservices, serverless API via AWS Lambda, and authentication using Okta’s easy and convenient identity provider. Each of these technologies is deserving of their own deep-dive article, so there is a lot to cover on these subjects. This post is meant to provide a starting point implementing all three. It will be simple so that anyone can follow along. It will also be kept to a clean, basic implementation that you can expand easily if needed.

Let’s jump right in and build our first microservice - an authentication service.

There are a few things you will need to follow this tutorial.

Visual Studio 2017 If you don’t have it, you can download Visual Studio Community for free. An AWS Account. If you don’t already have one, you can create one here.

Create Your ASP.NET Core Microservice

Create a new AWS Serverless Application (.NET Core) project as shown below. If you do not have this option you need to install the AWS Toolkit for Visual Studio.

You’ll also want to install the Newtonsoft.Json package as shown in the image below.

A microservice architecture is particularly empowering when it comes to scalability. Each component is isolated in its own executable or web service. This means components can run on dedicated hardware, and specific pieces of functionality can be scaled up and down with more granular precision than in an ESB (Enterprise Service Bus). This makes AWS Lambda a complementary medium to host your new microservice. Lambda is a serverless hosting solution that’s highly scalable.

Configure Authentication for Your ASP.NET Microservice

For this example, you’ll need an Okta developer account. If you don’t already have one you can sign up for a free developer account.

You’ll also need an Okta ApiToken. For security reasons, ApiTokens are only displayed once, so make sure you keep it safe once it’s generated. Login to your Okta Developer account, and click API in the main menu, then go to Tokens. On the Tokens screen click Create Token in the top left-hand corner of the page. Give your token a name then click Create Token.

At this point, Okta will generate your ApiToken and display it on the screen. This value will eventually be placed in appSettings.json, but for now, copy it somewhere to keep for later.

Determine the Scope of Your ASP.NET Core Microservice

The utility microservices provide is the loose coupling of dependencies. Everything inside your service is tightly coupled. Everything outside is loosely coupled. Keep this in mind while trying to determine how big your microservice should be. You don’t want your service to grow to the point where your writing class libraries that are tightly-coupled dependencies of more than one “micro-service”.

Following this rule, a microservice could offer a very deep pool of functionality, that can always be made deeper but should very rarely (if ever) be made wider.

Create the Objects for Your ASP.NET Core Microservice

In your project’s root folder, create a folder called Model. Inside that file, you’ll need to create the following model objects. It’s easiest to create all these first so that the objects for transferring data will already be in place when we start writing our logic.

OktaSettings

The OktaSettings object will be used to store your Okta domain and the API token that enables programmatic access to your Okta account. Notice that these members - Domain and ApiToken - match the attributes in our appSettings.json file under the Okta heading. When the program is initiated, the config file will be read and the values will be stored in memory, where they will then be accessible via IOptions<OktaSettings>.

namespace AuthenticationService.Model
{
    public class OktaSettings
    {
        public string Domain { get; set; }
        public string ApiToken { get; set; }
    }
}


OktaAuthenticationRequest

This class is used to send data to the Okta Authentication API at <span

class="okta-preview-domain"><span

class="okta-preview-domain">[https://{yourOktaDomain}</span></span>/api/v1/authn](https://{yourOktaDomain}/api/v1/authn "https://{yourOktaDomain}</span></span>/api/v1/authn"). An instance of this class will be passed to Okta with the username and password supplied to our authentication service. This is the call that tells us whether the login was successful.

For more information on Okta’s authentication API, including postman examples of HTTP requests, click here.

namespace AuthenticationService.Model
{
    public class OktaAuthenticationRequest
    {
        public string username { get; set; }
        public string password { get; set; }
    }
}


Credentials

Our AuthenticationController will expose a single action - Authenticate. That method accepts a single input - Credentials. Credentials is used to pass the username and password in need of authentication to our microservice.

namespace AuthenticationService.Model
{
    public class Credentials
    {
        public string PublicKey { get; set; }
        public string PrivateKey { get; set; }
    }
}


Keeping your microservice small, with only a single controller and a single action, has beneficial consequences downstream on systems dependent upon your new microservice. On a microservice driven platform, disasters are isolated. If one service goes down, the rest of the services and the unrelated features of the application will still be operational. The impacted portion of the app can display a friendly message informing the user of the outage while your team sets to work fixing the production issue. If the service logic and app logic were tightly coupled, the entire app would be brought down with the service.

AuthenticationResponse

Just like Credentials is used to pass data to the Authenticate action, AuthenticationResponse is used to return data from the Authenticate action. It will tell the client calling your microservice if the call was successful or it will return an error message.

In the event of success, you will also define the User and Session members.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticationResponse
    {
        public bool WasSuccessful { get; set; }
        public string Message { get; set; }

        public AuthenticatedSession Session { get; set; }
        public AuthenticatedUser User { get; set; }
    }
}


AuthenticatedSession

The AuthenticatedSession class is made to encapsulate details about the user’s session if they are successfully authenticated. This will be used to return that data to the client calling your microservice.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticatedSession
    {
        public string Token { get; set; }
        public string ExpiresAt { get; set; }
    }
}


AuthenticatedUser

Much like AuthenticatedSession, AuthenticatedUser is made to encapsulate details about the user, if they are successfully authenticated. This will be used to return that data to the client calling our microservice.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticatedUser
    {
        public string Id { get; set; }
        public DateTime PasswordChanged { get; set; }
        public string Login { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Locale { get; set; }
        public string TimeZone { get; set; }
    }
}


Load the Configuration for the ASP.NET Core Microservice

At startup, we’ll want to process the config file so that we can get access to the configured Okta Domain, and ApiToken. To do this add the following lines to the bottom of the ConfigureServices method of your Startup.cs file. This tells dot net core where the config details are, and gives it our OktaSettings class, to be used for accessing the configuration settings.

// Okta configuration
services.Configure<OktaSettings>(Configuration.GetSection("Okta"));
services.AddOptions();


Add the Authentication Steps to Your ASP.NET Core Microservice

The next step is to implement authentication by calling Okta’s Authentication API. Add a file called AuthenticationController.cs to your Controllers folder. Then paste the following code:

using System;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Text;

using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc;

using AuthenticationService.Model;

namespace AuthenticationService.Controllers
{
    [Route("api/[controller]/[action]")]
    public class AuthenticationController : Controller
    {
        private IOptions<OktaSettings> settings;
        public AuthenticationController(IOptions<OktaSettings> settings)
        {
            this.settings = settings;
        }

        [HttpPost]
        public async Task<AuthenticationResponse> Authenticate(Credentials input)
        {
            var ret = new AuthenticationResponse();

            AuthenticatedSession session = null;
            AuthenticatedUser user = null;

            //generate URL for service call using your configured Okta Domain
            string url = string.Format("{0}/api/v1/authn", this.settings.Value.Domain);

            //build the package we're going to send to Okta
            var data = new OktaAuthenticationRequest() { username = input.PublicKey, password = input.PrivateKey };

            //serialize input as json
            var json = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");

            //create HttpClient to communicate with Okta's web service
            using (HttpClient client = new HttpClient())
            {
                //Set the API key
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SSWS", this.settings.Value.ApiToken);

                //Post the json data to Okta's web service
                using (HttpResponseMessage res = await client.PostAsync(url, json))

                //Get the response from the server
                using (HttpContent content = res.Content)
                {
                    //get json string from the response
                    var responseJson = await content.ReadAsStringAsync();
                    Console.WriteLine(responseJson);

                    //deserialize json into complex object
                    dynamic responseObj = JsonConvert.DeserializeObject(responseJson);

                    //determine if the returned status is success
                    if (responseObj.status == "SUCCESS")
                    {
                        //get session data
                        session = new AuthenticatedSession()
                        {
                            Token = responseObj.sessionToken,
                            ExpiresAt = responseObj.expiresAt
                        };

                        //get user data
                        user = new AuthenticatedUser()
                        {
                            Id = responseObj._embedded.user.id,
                            Login = responseObj._embedded.user.login,
                            Locale = responseObj._embedded.user.locale,
                            TimeZone = responseObj._embedded.user.timeZone,
                            FirstName = responseObj._embedded.user.firstName,
                            LastName = responseObj._embedded.user.lastName
                        };
                    }
                }
            }

            //return results of the operation packaged in a AuthenticationResponse object
            var wasSuccess = session != null && user != null;
            return new AuthenticationResponse()
            {
                WasSuccessful = wasSuccess,
                Message = wasSuccess ? "Success" : "Invalid username and password",

                Session = session,
                User = user
            };
        }
    }
}


You may notice that the constructor of the class accepts an instance of IOptions<OktaSettings>. OktaSettings is the same class you defined above in your Model folder to store your Okta configuration settings.

The configuration code you added to Startup.cs enables IOptions<OktaSettings> to be recognized by .Net Core and the framework will automatically supply the input when the class is initialized. This gives you access to the Okta configuration settings in appSettings.json.

The Authenticate action has been well commented to help you understand what each line is doing. Here are the high-level steps that the action goes through:

Creates an instance of OktaAuthenticationRequest to pass the supplied username and password to Okta’s authentication service JSON encodes the data Creates an instance of HttpClient to communicate with Okta, and sets the authentication header value using the ApiToken Posts the JSON encoded data to Okta and awaits a response Deserialized JSON response stream, and evaluates if "SUCCESS" was the returned status If the login was unsuccessful it returns false for WasSuccess and "Invalid username and password" for Message. If it was a success it returns true for WasSuccess, and returns the session and user data provided by Okta

Add Configuration Management to Your Microservice

All your configuration for the service can be found in the appSettings.json file. Following best practice, you’ll want to configure each environment to point at a different instance of Okta. This isolates each environment so tests in one environment don’t interfere with tests in another. It helps you become familiar with your Okta settings so that you can be deliberate when configuring Okta and your software. It presents an opportunity to rehearse deployment and configuration. All of these things are useful steps you don’t want to skip when working your solution to production.

You’ll have to populate the below settings from your Okta account.

  "Okta": {
    "Domain": "https://{yourOktaDomain}",
    "ApiToken": "{yourApiToken}"
  }


Deploy Your Lambda Microservice

Deploying your new microservice as a Lambda function is easy. The AWS Toolkit for Visual Studio will create the cloud components for you. To deploy your microservice, in the Solution Explorer, right click on your web service project, and select Publish to AWS Lambda.

Click on the little person icon next to Account Profile to Use. In the resulting pop-up window, you can use “default” as the profile name. You’ll need to enter Access Key information. To create an Access Key in AWS, click on your account name in the top right of your AWS dashboard and choose My Security Credentials. Expand the Access Keys section and click Create New Access Key. In the pop-up window, there will be a link to show the Access Key ID and Secret Access Key. You can enter them in the deployment window in Visual Studio, or download the keyfile and click Import from csv file… to import it.

Then, you’ll need to get Provide a Stack Name. This can be whatever you want to name it and will be used by the wizard when creating your AWS resource to prefix automatically generated component names. You can also provide the S3 Bucket you’d like the code deployed to. You can click the New… button to create an S3 Bucket to use. Click Publish to start the deployment.

If your cloud resources have not already been created, publish will create them for you. During publish, the modal window will disappear and a deployment tab will appear in your IDE. Once the status shown in the tab displays UPDATE_COMPLETE, your deployment has finished.

Copy the AWS Serverless URL and use it to test your microservice. Your URL may look something like mine shown below: [https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod](https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod "https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod")

This means the URL to our authenticate action looks like: [https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate](https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate "https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate")

Open Postman and POST to the above URL, while sending the following form data:

  • PublicKey = {YOURUSERNAME}
  • PrivateKey = {YOURPASSWORD}

If you get a response back like the following, you’re good to go!

{
    "wasSuccessful": true,
    "message": "Success",
    "session": {
        "token": "2011129scHesxiG6Psr1ZCtdADBEJ1iBMr_KtTnaeSV4ZJvpP03BOP_",
        "expiresAt": "03/19/2019 18:32:20"
    },
    "user": {
        "id": "00uitftb3egstVhnQ0h7",
        "passwordChanged": "0001-01-01T00:00:00",
        "login": null,
        "firstName": null,
        "lastName": null,
        "locale": null,
        "timeZone": null
    }
}


ASP.NET Core Microservice Next Steps and Considerations for Production

If you’re going to take this to production you’re definitely going to want to have an SSL certificate. Select a certificate authority like AWS or GoDaddy, and check out their SSL offerings. Purchase your SSL and install it on your web server or AWS account.

Authentication is about verifying the identity of the client. Authorization, on the other hand, is what you sometimes do after you know who you’re dealing with and you want to know what they have permission to access. If your authorization logic is simple, it may be appropriate to add another controller and action to this existing microservice. If your logic is more complex, then it should be put into a new microservice. That new microservice could then be passed the session token (returned from the authentication microservice we built today), and the identity of a digital resource, and it will return the level of access to that resource.

Asp.Net Core MVC Bangla Tutorials -01 (Complete eCommerce Application)

Asp.Net Core MVC Bangla Tutorials -01 (Complete eCommerce Application)

LIKE | COMMENT | SHARE | SUBSCRIBE The easiest way to start coding in Visual Studio is by creating a new project from a template – we’ll cover working with e...

ASP.NET MVC Application with JQuery, AJAX

ASP.NET MVC Application with JQuery, AJAX

Let's have a sample example: We have a submit button in our JQuery AJAX MVC application. Let's try to show a message when it is clicked. ASP.NET MVC Application - Using JQuery, AJAX

Before diving into the core topic let’s have an overview about JQuery and Ajax. What is it?

What is JQuery?
Well, JQuery is a framework (tools) for writing JavaScript, Written as "write less, do more", jQuery is to make easier to use JavaScript.

What is JavaScript?
JavaScript is an object-oriented computer programming (Scripting) language commonly used to create interactive effects within web browsers.

Let’s have a sample example:
We have a submit button in our JQuery AJAX MVC application. Let’s try to show a message when it is clicked.

Here is our button with defined id="btnSubmit":

<input type="submit" value="Create" class="btn btn-default" id="btnSubmit" />

Now we need to write some script to do that, here is some JavaScript code:

var myBtn = document.getElementById('btnSubmit');  
myBtn.addEventListener('click', function(e) {  
    e.preventDefault(); //This prevents the default action  
    alert('Hello'); //Show the alert  
});

By this code the result will show “Hello”:

Now if we can get the same result by using jQuery instead of it. Let’s have a look:

$('#btnSubmit').click(function(event) {  
    event.preventDefault(); //This prevents the default action  
    alert("Hello"); //Show the alert  
});

Note: Here use of 'e' is just a short for event which raised the event. We can pass any variable name. Notice that the ‘e’ is changed to name ‘event’ in JQuery part.

This piece of code is also producing the same result “Hello”. This is why jQuery is known as "write less, do more".

Finally the script:

<script type="text/javascript">  
    $(document).ready(function() {  
        $(function() {  
  
            /*var myBtn = document.getElementById('btnSubmit');  
            myBtn.addEventListener('click', function(e) {  
                e.preventDefault();  
                alert('Hello');  
            });*/
  
            $('#btnSubmit').click(function(event) {  
                event.preventDefault();  
                alert("Hello");  
            });  
        });  
    });  
</script>

Let's focus on Ajax:

AJAX stands for “Asynchronous JavaScript and XML”. AJAX is about exchanging data with a server, without reloading the whole page. It is a technique for creating fast and dynamic web pages.

In .NET, we can call server side code using two ways:

  1. ASP .NET AJAX
  2. jQuery AJAX

In this article we will focus on JQuery Ajax.

$.ajax () Method:

JQuery’s core method for creating Ajax requests. Here are some jQuery AJAX methods:

  • $.ajax() Performs an async AJAX request.
  • $.get() Loads data from a server using an AJAX HTTP GET request.
  • $.post() Loads data from a server using an AJAX HTTP POST request.

To know more click.

$.ajax () Method Configuration option:

Options that we use:

  • async:
  • type:
  • url:
  • data:
  • datatype:
  • success:
  • error:

Let’s have details overview:

async

Set to false if the request should be sent synchronously. Defaults to true.

Note that if you set this option to false, your request will block execution of other code until the response is received.

Example:

async: false,

type

This is type of HTTP Request and accepts a valid HTTP verb.

The type of the request, "POST" or "GET" defaults to "GET". Other request types, such as "PUT" and "DELETE" can be used, but they may not be supported by all the web browsers.

Example:

type: "POST",

url

The URL for the request.

Example:

url: "/Home/JqAJAX",

data

The data to be sent to the server. This can either be an object or a query string.

Example:

data: JSON.stringify(model_data),

dataType

The type of data you expect back from the server. By default, jQuery will look at the MIME type of the response if no dataType is specified.

Accepted values are text, xml, json, script, html jsonp.

Example:

dataType: "json",

contentType

This is the content type of the request you are making. The default is 'application/x-www-form-urlencoded'.

Example:

contentType: 'application/json; charset=utf-8',

success

A callback function to run if the request succeeds. The function receives the response data (converted to a JavaScript object if the DataType was JSON), as well as the text status of the request and the raw request object.

Success: function (result) {  
   $('#result').html(result);  
}

error

A callback function to run if the request results in an error. The function receives the raw request object and the text status of the request.

error: function (result) {  
   alert('Error occured!!');  
},

Let’s Post Values using JQuey,Ajax:

We often use the jQuery Ajax method in ASP.NET Razor Web Pages. Here is a sample code:

<input type="submit" id="btnSubmit" value="Add Student" />  
<script type="text/javascript">  
    $(document).ready(function() {  
        $(function() {  
            $('#btnSubmit').click(function(event) {  
                event.preventDefault();  
                var Student = {  
                    ID: '10001',  
                    Name: 'Shashangka',  
                    Age: 31  
                };  
                $.ajax({  
                    type: "POST",  
                    url: "/Home/JqAJAX",  
                    data: JSON.stringify(Student),  
                    dataType: "json"  
                    contentType: 'application/json; charset=utf-8',  
                    success: function(data) {  
                        alert(data.msg);  
                    },  
                    error: function() {  
                        alert("Error occured!!")  
                    }  
                });  
            });  
        });  
    });  
</script>

Controller Action:

// GET: Home/JqAJAX  
[HttpPost]  
public ActionResult JqAJAX(Student st) {  
    try {  
        return Json(new {  
            msg = "Successfully added " + st.Name  
        });  
    } catch (Exception ex) {  
        throw ex;  
    }  
}

Posting JSON

JSON is a data interchange format where values are converted to a string. The recommended way to create JSON is include the JSON.stringify method. In this case we have defined:

JSON.stringify(Student)

And the data type set to:

datatype: "json"

And the content type set to application/json

contentType: 'application/json; charset=utf-8'

Syntax:

JSON.stringify(value[, replacer[, space]])

Post Script:

var Student = {  
    ID: '10001',  
    Name: 'Shashangka',  
    Age: 31  
};  
$.ajax({  
    type: "POST",  
    url: "/Home/JqAJAX",  
    data: JSON.stringify(Student),  
    contentType: 'application/json; charset=utf-8',  
    success: function(data) {  
        alert(data.msg);  
    },  
    error: function() {  
        alert("Error occured!!")  
    }  
});

Controller Action:

// GET: Home/JqAJAX  
[HttpPost]  
public ActionResult JqAJAX(Student st) {  
    try {  
        return Json(new {  
            msg = "Successfully added " + st.Name  
        });  
    } catch (Exception ex) {  
        throw ex;  
    }  
}

JSON Response Result:
Sent data format:
{"ID":"10001","Name":"Shashangka","Age":31}
Received Data format:
{"msg":"Successfully added Shashangka"}

Let’s Post JavaScript Objects:

To send JavaScript Objects we need to omit the JSON.stringify(Student) method and we need to pass the plain object to the data option. In this case we have defined:

data: Student

And the datatype set to:

datatype: "html"

And the content type set to default.

contentType: 'application/x-www-form-urlencoded'

Post Script

var Student = {  
    ID: '10001',  
    Name: 'Shashangka',  
    Age: 31  
};  
$.ajax({  
    type: "POST",  
    url: "/Home/JqAJAX",  
    data: Student,  
    contentType: 'application/x-www-form-urlencoded',  
    datatype: "html",  
    success: function(data) {  
        alert(data.msg);  
    },  
    error: function() {  
        alert("Error occured!!")  
    }  
});

Controller Action:

var Student = {  
    ID: '10001',  
    Name: 'Shashangka',  
    Age: 31  
};  
$.ajax({  
    type: "POST",  
    url: "/Home/JqAJAX",  
    data: Student,  
    contentType: 'application/x-www-form-urlencoded',  
    datatype: "html",  
    success: function(data) {  
        alert(data.msg);  
    },  
    error: function() {  
        alert("Error occured!!")  
    }  
});

JavaScript Objects Response Result:

Sent data format:
ID=10001&Name=Shashangka&Age=31
Received Data format:
{"msg":"Successfully added Shashangka"}

Let’s Post JavaScript Arrays:

To send Array we need to omit the JSON.stringify(Student) method and we need to pass the plain object to the data option. In this case we have defined:

data: Student

And the datatype set to:

datatype: "html"

And the content type set to default

contentType: 'application/x-www-form-urlencoded'

Post Script:

var ID = ["Shashangka", "Shekhar", "Mandal"];  
  
$.ajax({  
    type: "POST",  
    url: "/Home/JqAJAX",  
    data: {  
        values: ID  
    },  
    datatype: "html",  
    contentType: 'application/x-www-form-urlencoded',  
    success: function(data) {  
        alert(data.msg);  
    },  
    error: function() {  
        alert("Error occured!!")  
    }  
});

Controller Action:

// GET: Home/JqAJAX  
[HttpPost]  
public ActionResult JqAJAX(string[] values) {  
    try {  
        return Json(new {  
            msg = String.Format("Fist Name: {0}", values[0])  
        });  
    } catch (Exception ex) {  
        throw ex;  
    }  
}

Array Response Result:

Sent data format:
values[]=Shashangka&values[]=Shekhar&values[]=Mandal
Received Data format:
{"msg":"Fist Name: Shashangka"}

Hope this will help to understand different datatypes and Ajax posting. Thank you!

How to Install Bootstrap in ASP.NET MVC 5

How to Install Bootstrap in ASP.NET MVC 5

In this post, I will share the way to install Bootstrap in ASP.NET MVC5 using Visual Studio 2019

Continuing with the previous share, today I will share the way to install Bootstrap in ASP.NET MVC5 using Visual Studio 2019.

Setup Bootstrap 3.3.1 to ASP.NET MVC 5

Right-click Project -> Manager NuGet Packages -> Click icon setting configuration, add package source.

https://api.nuget.org/v3/index.json

Continue by adding a package source (following image)

You search keyword “bootstrap” and then install

You can see bootstrap in project after a successful installation.

Okay, installation is successful!

You can use bootstrap in Layout file of _Views/Shared/LayoutHome.cshtml directory. (Note you add css,javascript to file)

<link rel="stylesheet" href="~/Content/bootstrap.min.css" />  
<script src="~/Scripts/jquery-3.3.1.min.js"></script>  
<script src="~/Scripts/bootstrap.min.js"></script>  

Example - _Views/Shared/_LayoutHome.cshtml _

<!DOCTYPE html>  
<html>  
<head>  
    <meta name="viewport" content="width=device-width" />  
    <title>@ViewBag.Title</title>  
    <link rel="stylesheet" href="~/Content/bootstrap.min.css" />  
    <script src="~/Scripts/jquery-3.3.1.min.js"></script>  
    <script src="~/Scripts/bootstrap.min.js"></script>  
</head>  
<body>  
    <div>  
        @RenderBody()  
    </div>  
</body>  
</html>  

_Views/Home/Index.cshtml _

<div class="container">  
    <div class="row">  
        <div class="col-md-12">  
            <h2>@ViewBag.title</h2>  
            <a href="https://hoanguyenit.com" class="btn btn-success">https://hoanguyenit.com</a>  
        </div>  
    </div>  
</div>  

Ok, we successfully installed bootstrap in ASP.NET MVC 5!! Thank you for reading!