Showing posts with label Web API. Show all posts
Showing posts with label Web API. Show all posts

Tuesday, November 28, 2023

Using Mongo DB in an ASP.NET 8.0 Minimal Web API web app

 In this tutorial I will show you how to develop an ASP.NET 8.0 Minimal API application that interacts with MongoDB. In order to proceed you will need the following pre-requisites:

  • Docker - used to host MongoDB. This is my preferred approach because it is quick and does not take too many resources on the host computer.
  • .NET 8.0
  • Visual Studio Code

The Database

Run the following command in a terminal window to start a MongoDB container named mdb:

docker run -p 27777:27017 --name mgo -d mongo:4.1.6

You can verify that the container is running by typing the following command in a terminal window:

docker ps -a

Let us start a bash session inside the container so that we can create a database and add some sample data to it. Enter the following command to start an interactive bash session inside the container:

docker exec -it mgo bash

We can then use the mongo command line interface (CLI) to create a database named school-db and then add some sample data to a collection of students. Type the following command:

mongo

This takes you into a mongo session command prompt. Enter the following command to create a database named school-db:

use school-db

Next, let's create a collection named students and add to it six sample students:

db.Students.insertMany(
[
  {'FirstName':'Sue','LastName':'Fox','School':'Business'},
  {'FirstName':'Tom','LastName':'Max','School':'Mining'},
  {'FirstName':'Ann','LastName':'Lee','School':'Nursing'},
  {'FirstName':'Joe','LastName':'Roy','School':'Tourism'},
  {'FirstName':'Jan','LastName':'Ash','School':'Communications'},
  {'FirstName':'Eva','LastName':'Day','School':'Medicine'},
]);

You can retrieve the list of students with the following command:

db.Students.find().pretty();

To exit the mongo command prompt by typing:

exit

You can also exit the bash session and return to the host operating system by typing:

exit

Creating an ASP.NET 8.0 Minimal Web API application

Create an ASP.NET 8.0 Minimal Web API application. This is accomplished by entering this command:

dotnet new webapi -f net8.0 -o AspMongoApi

Let's see what our application looks like. Run the application by executing:

cd AspMongoApi
dotnet run

The above command builds and runs the application in a web server. Point your browser to https://localhost:????/weatherforecast (Where ???? is your port number). This is what you should see:


Stop the web server by hitting CTRL+C on the keyboard.

Building the Students API

Add the MongoDB driver Nuget package with the following command:

dotnet add package MongoDB.Driver

Now that we have the driver, we can proceed with coding our Minimal API. I will use Visual Studio Code because it is operating system agnostic. To load your application workspace into VS Code, simply type in the following command from the root folder of your application:

code .

We need a Student class to represent the schema for student documents in a Students collection in the MongoDB school-db database. Create a Models folder. Inside the Models folder, add a file named Student.cs with the following code:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace AspMongoApi.Models;
public class Student {
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string? Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    
    [BsonElement("School")]
    public string? Department { get; set; }
}

In the preceding class, the Id property:
  • Is required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
  • Is annotated with [BsonId] to designate this property as the document's primary key.
  • Is annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.
The Department property is annotated with the [BsonElement] attribute. The attribute's value of School represents the property name in the MongoDB collection.

Add the following to appsettings.json:

"StudentDbSettings": {
"CollectionName": "Students",
"ConnectionString": "mongodb://localhost:27777",
"DatabaseName": "school-db"
},

The entire contents of appsettings.json will look like this:

{
  "StudentDbSettings": {
    "CollectionName": "Students",
    "ConnectionString": "mongodb://localhost:27777",
    "DatabaseName": "school-db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Add StudentDbSettings.cs to the Models folder with this code:

namespace AspMongoApi.Models;

public class StudentsDbSettings {
    public string ConnectionString { get; set; } = null!;
    public string DatabaseName { get; set; } = null!;
    public string CollectionName { get; set; } = null!;
}

The above StudentDbSettings class is used to store the appsettings.json file's StudentDbSettings property values. The JSON and C# property names are named identically to simplify the mapping process.

Add the following code to Program.cs before "var app = builder.Build();" :

builder.Services.Configure<StudentsDbSettings>(
    builder.Configuration.GetSection("StudentDbSettings"));

In the above code, the configuration instance to which the appsettings.json file's StudentDatabaseSettings section binds is registered in the Dependency Injection (DI) container.

Create a folder named Services and add to it a file named StudentService.cs with the following code:

namespace AspMongoApi.Services;

public class StudentsService {
    private readonly IMongoCollection<Student> _studentsCollection;

    public StudentsService(IOptions<StudentsDbSettings> studentsDatabaseSettings) {
        var mongoClient = new MongoClient(
            studentsDatabaseSettings.Value.ConnectionString);

        var mongoDatabase = mongoClient.GetDatabase(
            studentsDatabaseSettings.Value.DatabaseName);

        _studentsCollection = mongoDatabase.GetCollection<Student>(
            studentsDatabaseSettings.Value.CollectionName);
    }

    public async Task<List<Student>> GetAsync() =>
        await _studentsCollection.Find(_ => true).ToListAsync();

    public async Task<Student?> GetAsync(string id) =>
        await _studentsCollection.Find(x => x.Id == id).FirstOrDefaultAsync();

    public async Task CreateAsync(Student newStudent) =>
        await _studentsCollection.InsertOneAsync(newStudent);

    public async Task UpdateAsync(string id, Student updatedStudent) =>
        await _studentsCollection.ReplaceOneAsync(x => x.Id == id, updatedStudent);

    public async Task RemoveAsync(string id) =>
        await _studentsCollection.DeleteOneAsync(x => x.Id == id);
}

In the above code, an StudentsDbSettings instance is retrieved from Dependency Injection via constructor injection. Also, the above class knows how to connect to the MongoDB server and use the available driver methods to retrieve, insert, update and delete data.

Register the StudentService class with DI to support constructor injection. Add the following to Program.cs before "var app = builder.Build();" :

builder.Services.AddSingleton<StudentsService>();

Add the following code to Program.cs right before the "app.Run()l" statement:

app.MapGet("/students", async (StudentsService studentsService) => {
    var students = await studentsService.GetAsync();
    return students;
});

app.MapGet("/students/{id}", async (StudentsService studentsService, string id) => {
    var student = await studentsService.GetAsync(id);
    return student is null ? Results.NotFound() : Results.Ok(student);
});

app.MapPost("/students", async (StudentsService studentsService, Student student) => {
    await studentsService.CreateAsync(student);
    return student;
});

app.MapPut("/students/{id}", async (StudentsService studentsService, string id, Student student) => {
    await studentsService.UpdateAsync(id, student);
    return student;
});

app.MapDelete("/students/{id}", async (StudentsService studentsService, string id) => {
    await studentsService.RemoveAsync(id);
    return Results.Ok();
});

The above endpoints:
  • Use the StudentService class to perform CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.

Running the application

Start the application by executing the this command from a terminal windows at the root of the application:

dotnet watch

Test the API with the Swagger interface that is available in your browser:




You can also view the data by pointing your browser to https://localhost:????/api/Students. This is the expected output:


Note that even though the last field is named School in the database, it appears as Department in our app because that is how we mapped it in the Student class.

Go ahead and test the other API endpoints for POST, PUT and DELETE using your favourite tool like Postman or curl. It should all work.

Cleanup

To stop & remove the MongoDB docker container, enter the following from any terminal window on your computer:

docker rm -f mgo

I hope you learned something new in this tutorial. Until next time, happy coding.

Saturday, February 5, 2022

ASP.NET 6.0 Web API and Mongo DB

In this tutorial I will show you how to develop an ASP.NET 6.0 API application that interacts with MongoDB. In order to proceed you will need the following pre-requisites:

  • Docker - used to host MongoDB. This is my preferred approach because it is quick and does not take too many resources on the host computer.
  • .NET 6.0
  • Visual Studio Code

The Database

Run the following command in a terminal window to start a MongoDB container named mdb:

docker run -p 27777:27017 --name mgo -d mongo:4.1.6

You can verify that the container is running by typing the following command in a terminal window:

docker ps -a

Let us start a bash session inside the container so that we can create a database and add some sample data to it. Enter the following command to start an interactive bash session inside the container:

docker exec -it mgo bash

We can then use the mongo command line interface (CLI) to create a database and then add some sample data to a collection of students. Type the following command:

mongo

This takes you into a mongo session command prompt. Enter the following command to create a database named school-db:

use school-db

Next, let's create a collection named students and add to it six sample students:

db.Students.insertMany(
[
  {'FirstName':'Sue','LastName':'Fox','School':'Business'},
  {'FirstName':'Tom','LastName':'Max','School':'Mining'},
  {'FirstName':'Ann','LastName':'Lee','School':'Nursing'},
  {'FirstName':'Joe','LastName':'Roy','School':'Tourism'},
  {'FirstName':'Jan','LastName':'Ash','School':'Communications'},
  {'FirstName':'Eva','LastName':'Day','School':'Medicine'},
]);

You can retrieve the list of students with the following command:

db.Students.find({}).pretty();

To exit the mongo command prompt by typing:

exit

You can also exit the bash session and return to the host operating system by typing:

exit

Creating an ASP.NET 6.0 Web API application

Create an ASP.NET 6.0 Web API application. This is accomplished by entering this command:

dotnet new webapi -f net6.0 -o AspMongoApi

Let us see what our application looks like. Run the application by executing:

cd AspMongoApi
dotnet run

The above command builds and runs the application in a web server. Point your browser to https://localhost:????/weatherforecast (Where ???? is your port number). This is what you should see:


Stop the web server by hitting CTRL+C on the keyboard.

Building the Students API

Find the latest version of MongoDB driver from https://www.nuget.org/packages/MongoDB.Driver/. At the time of writing, the latest version was 2.14.1. Add the MongoDB driver Nuget package with the following command:

dotnet add package MongoDB.Driver --version 2.14.1

Now that we have the driver, we can proceed with coding our API. I will use Visual Studio Code because it is operating system agnostic. To load your application workspace into VS Code, simply type in the following command from the root folder of your application:

code .

We need a Student class to represent the schema for student documents in the Students collection in the MongoDB school-db database. Create a Models folder. Inside the Models folder, add a file named Student.cs with the following code:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace AspMongoApi.Models;
public class Student {
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string? Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    
    [BsonElement("School")]
    public string? Department { get; set; }
}

In the preceding class, the Id property:
  • Is required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
  • Is annotated with [BsonId] to designate this property as the document's primary key.
  • Is annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.
The Department property is annotated with the [BsonElement] attribute. The attribute's value of School represents the property name in the MongoDB collection.
Add the following to appsettings.json:

"StudentDbSettings": {
"CollectionName": "Students",
"ConnectionString": "mongodb://localhost:27777",
"DatabaseName": "school-db"
},

The entire contents of appsettings.json will look like this:

{
  "StudentDbSettings": {
    "CollectionName": "Students",
    "ConnectionString": "mongodb://localhost:27777",
    "DatabaseName": "school-db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Add StudentDbSettings.cs to the Models folder with this code:

namespace AspMongoApi.Models;

public class StudentsDbSettings {
    public string ConnectionString { get; set; } = null!;
    public string DatabaseName { get; set; } = null!;
    public string CollectionName { get; set; } = null!;
}

The above StudentDbSettings class is used to store the appsettings.json file's StudentDbSettings property values. The JSON and C# property names are named identically to simplify the mapping process.

Add the following code to Program.cs before "var app = builder.Build();" :

builder.Services.Configure<StudentsDbSettings>(
    builder.Configuration.GetSection("StudentDbSettings"));

In the above code, the configuration instance to which the appsettings.json file's StudentDatabaseSettings section binds is registered in the Dependency Injection (DI) container.

Create a folder named Services and add to it a file named StudentService.cs with the following code:

namespace AspMongoApi.Services;

public class StudentsService {
    private readonly IMongoCollection<Student> _studentsCollection;

    public StudentsService(IOptions<StudentsDbSettings> studentsDatabaseSettings) {
        var mongoClient = new MongoClient(
            studentsDatabaseSettings.Value.ConnectionString);

        var mongoDatabase = mongoClient.GetDatabase(
            studentsDatabaseSettings.Value.DatabaseName);

        _studentsCollection = mongoDatabase.GetCollection<Student>(
            studentsDatabaseSettings.Value.CollectionName);
    }

    public async Task<List<Student>> GetAsync() =>
        await _studentsCollection.Find(_ => true).ToListAsync();

    public async Task<Student?> GetAsync(string id) =>
        await _studentsCollection.Find(x => x.Id == id).FirstOrDefaultAsync();

    public async Task CreateAsync(Student newStudent) =>
        await _studentsCollection.InsertOneAsync(newStudent);

    public async Task UpdateAsync(string id, Student updatedStudent) =>
        await _studentsCollection.ReplaceOneAsync(x => x.Id == id, updatedStudent);

    public async Task RemoveAsync(string id) =>
        await _studentsCollection.DeleteOneAsync(x => x.Id == id);
}

In the above code, an StudentsDbSettings instance is retrieved from DI via constructor injection. Also, the above class knows how to connect to the MongoDB server and use the available driver methods to retrieve, insert, update and delete data.

To register the StudentService class with DI to support constructor injection, add the following to Program.cs before "var app = builder.Build();" :

builder.Services.AddSingleton<StudentsService>();

Add a StudentsController.cs class to the Controllers folder with the following code:

namespace AspMongoApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class StudentsController : ControllerBase {
    private readonly StudentsService _studentsService;

    public StudentsController(StudentsService studentsService) =>
        _studentsService = studentsService;

    [HttpGet]
    public async Task<List<Student>> Get() =>
        await _studentsService.GetAsync();

    [HttpGet("{id:length(24)}")]
    public async Task<ActionResult<Student>> Get(string id) {
        var student = await _studentsService.GetAsync(id);

        if (student is null) {
            return NotFound();
        }

        return student;
    }

    [HttpPost]
    public async Task<IActionResult> Post(Student newStudent) {
        await _studentsService.CreateAsync(newStudent);

        return CreatedAtAction(nameof(Get), new { id = newStudent.Id }, newStudent);
    }

    [HttpPut("{id:length(24)}")]
    public async Task<IActionResult> Update(string id, Student updatedStudent) {
        var student = await _studentsService.GetAsync(id);

        if (student is null) {
            return NotFound();
        }

        updatedStudent.Id = student.Id;

        await _studentsService.UpdateAsync(id, updatedStudent);

        return NoContent();
    }

    [HttpDelete("{id:length(24)}")]
    public async Task<IActionResult> Delete(string id) {
        var student = await _studentsService.GetAsync(id);

        if (student is null) {
            return NotFound();
        }

        await _studentsService.RemoveAsync(student.Id!);

        return NoContent();
    }
}

The StudentsController class:
  • Uses the StudentService class to perform CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.

Running the application

Start the application by executing the this command from a terminal windows at the root of the application:

dotnet run

You can view the data by pointing your browser to https://localhost:????/api/Students. This is the expected output:


Note that even though the last field is named School in the database, it appears as Department in our app because that is how we mapped it in the Student class.

Go ahead and test the other API endpoints for POST, PUT and DELETE using your favourite tool like Postman or curl. It should all work.

Cleanup

To stop & remove the MongoDB docker container, enter the following from any terminal window on your computer:

docker rm -f mgo

I hope you learned something new in this tutorial. Until next time, happy coding.

Reference:https://referbruv.com/blog/posts/integrating-mongodb-with-aspnet-core-(net-6)

Thursday, August 13, 2020

Deploying an ASP.NET Core application that uses LocalDB to IIS on Windows 10

 LocalDB is intended as a light-weigh development version SQL Server and is not supposed to be used in production.  Similarly, it is not the best version of SQL Server to be used with a web app hosted on IIS. 

Having said that, there are circumstances when a developer may wish to run an ASP.NET Core web app that works with LocalDB on IIS. This post explains one way I got this to work. 

Assumptions: It is assumed the following is running on your Windows 10 computer:

  • git
  • Visual Studio 2019 (or later)
  • LocalDB
  • .NET Core 3.1
  •  dotnet-ef tool 
  • Internet Information Services (IIS)

In this tutorial we will deploy an ASP.NET 3.1 Web API application that uses LocalDB to IIS . As a starting point, you can clone a simple ASP.NET Core 3.1 web application from https://github.com/medhatelmasry/SchoolAPI-3.1.git.

To clone the above application, go to a suitable working directory in a terminal window and run the following command:

git clone https://github.com/medhatelmasry/SchoolAPI-3.1.git

You can then go into the newly created SchoolAPI-3.1 directory with:

cd SchoolAPI-3.1

Let us quickly test this application to make sure that it works. Type the following in a terminal window:

dotnet-ef database update

dotnet run

You should see the following when you point your browser to http://localhost:5000/api/students:

Hit CTRL C to stop the server.

There is a solution (.sln) file in the root directory of the web app. Open this solution in Visual Studio 2019. This can easily be done by typing the following in the terminal window:

SchoolAPI.sln

Once in Visual Studio, edit the appsettings.json file and have a peek at the connection string, which looks like this:

"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=School;Trusted_Connection=True;MultipleActiveResultSets=true"

We will need to do a minor change to this connection string to make it work when our web app is hosted by IIS.

Since we are in Visual Studio 2019, let us run the web application by hitting CTRL F5. We should see the same result as we saw previously when we started the app from a terminal window. 

Install ASP.NET Core Hosting Bundle

For IIS to support ASP.NET Core, you will need to install the “ASP.NET Core Hosting Bundle”. Point your browser to https://dotnet.microsoft.com/download/dotnet-core/3.1. Click on the “Hosting Bundle” link.


A file named dotnet-hosting-3.1.6-win.exe gets downloaded to your computer. Go ahead and run this installer.

Publish web app to IIS

Let us now publish our application to IIS. Create a folder named SchoolAPI under c:\inetpub\wwwroot, which is the the root directory of IIS. 

Back in Visual Studio, right click on the SchoolAPI node in Solution Explorer and select Publish...

On the Publish dialog, select Folder, then click Next.

Use the Browse... button to select the c:\inetpub\wwwroot/SchoolAPI directory, then click on Finish.

 

Click on Publish.

Let us create a website in IIS. Start "Internet Information Services (IIS) Manager". Right-click on the Sites node then select "Add Website..."



Add a website as shown below.


Select the "Application Pools" node then double-click on the SchoolAPI pool in the center.

Select "No Managed Code" in the ".NET CLR version" selection list, then click OK.

Point your browser to http://localhost:8088/api/students. The web application will fail with an HTTP ERROR 500. Unfortunately, there is not enough information to tell us what the problem is. I will describe to you two simple ways of finding out what the problem is:

1) Start the Windows "Event Viewer". Select "Windows Logs" >> Application, then read the first couple of errors.


You will come across this error, which suggests that the app is unable to connect to SQL Server:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. 

2) To see a more descriptive error message in your browser, you can also set the environment variable ASPNETCORE_ENVIRONMENT to Development. Add this to the C:\inetpub\wwwroot\SchoolAPI\web.config file inside the <aspNetCore … > section. Once you do so, your < aspNetCore…> block will look like this:

<aspNetCore processPath="dotnet" arguments=".\SchoolAPI.dll" 

  stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" >

  <environmentVariables>

    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />

  </environmentVariables>

</aspNetCore>

Save the web.config file, then refresh the page in your browser. A more detailed error message will display.

One way to fix the database connectivity problem

I have one solution to this problem so that IIS can communicate with LocalDB. I welcome any other suggestions. 

From within a terminal window, execute the following commands:

sqllocaldb start MSSQLLocalDB
sqllocaldb info MSSQLLocalDB

You should experience output similar to the following:

C:\>sqllocaldb start MSSQLLocalDB
LocalDB instance "mssqllocaldb" started.

C:\>sqllocaldb info MSSQLLocalDB
Name:               mssqllocaldb
Version:            13.1.4001.0
Shared name:
Owner:              bingo
Auto-create:        Yes
State:              Running
Last start time:    2020-08-13 11:33:00 AM
Instance pipe name: np:\\.\pipe\LOCALDB#1961D8A7\tsql\query

We can use the "Instance pipe name" as the name of the server in the database connection string. Copy the value of the "Instance pipe name" without 'np:'. In the above example it would be '\\.\pipe\LOCALDB#1961D8A7\tsql\query'. Go to Visual Studio 2019, open appsettings.json and paste contents of your clipboard instead of '(localdb)\\mssqllocaldb' in the connection string. In my case, the connection string looked like this:

"DefaultConnection": "Server=\\\\.\\pipe\\LOCALDB#1961D8A7\\tsql\\query;Database=School;Trusted_Connection=True;MultipleActiveResultSets=true"

NOTE: When you pasted the name pipe, all \ are escaped to \\. This is OK and do not be alarmed.

If you run the web application in Visual Studio, it should be working as expected:

Now, let us do the same change to C:\inetpub\wwwroot\SchoolAPI\appsettings.json. You can simply replace the connection string in C:\inetpub\wwwroot\SchoolAPI\appsettings.json with the connection string from appsettings.json in Visual Studio 2019.

Thereafter, point your browser to http://localhost:8088/api/students. This time, we will get a different error:

SqlException: Login failed for user 'IIS APPPOOL\SchoolAPI'.

This error suggests that, although the database was found, IIS failed to login to the database using account 'IIS APPPOOL\SchoolAPI'. To fix this problem, we will get IIS to use a different account. Select the SchoolAPI application pool, then click on 'Advanced Settings..." on the right-side.


Click on the ... beside Identity.


Change the "Built-in account:" to LocalSystem, then click on OK followed by OK again.
By changing the account to LocalSystem, IIS will try and login to our database using a built-in account named 'NT AUTHORITY\SYSTEM'. We need to give this account access to our School database in LocalDB. 

Download and install SQL Server Management Studio (SSMS) from https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms.

Start SSMS. Use "Windows Authentication" to login into server name "(localdb)\MSSQLLocalDB".


Right-click on Security >> Logins and select "New Login...".
Click on Search...

Enter 'system'.

Click on "Check Names". When the account is found, you will see SYSTEM in uppercase and underlined.

Click on OK. Next, select the "User Mapping" node on the left side, check the School database, then check db_owner at the bottom.


Click on OK to close the Login - New dialog.

Browser to http://localhost:8088/api/students. The page should display data as expected.

NOTE: If you receive an error suggesting that the database cannot be found, then run these command again in a terminal window and update the "Instance pipe name" in the appsettings.json connection string.

sqllocaldb start MSSQLLocalDB
sqllocaldb info MSSQLLocalDB

As I said before, this is really an academic exercise and that LocalDB should never be used in production. SQL Express is a much better option.