Introduction
In this article series, we will learn how to create a .NET Core API that can do CRUD operations on Azure Cosmos DB and then publish it to Azure Cloud. After that, we will secure the API with Easy Auth using Facebook as Identity Provider.
This is Part 1 of the article series and the course objectives for this part are:
- Create a new Container and Item in Azure Cosmos DB
- Create .NET Core API to GET and POST and DELETE and PUT data to the Cosmos DB
Prerequisites
For the purposes of this demo, we will be using a database called FamilyDB and a container called FamilyDB. Before starting please make sure you have this setup ready. You can use the JSON item below to feed the initial data
{
"id": "AndersenFamily",
"lastName": "Andersen",
"parents": [
{ "firstName": "Thomas" },
{ "firstName": "Mary Kay"}
],
"children": [
{
"firstName": "Henriette Thaulow",
"gender": "female",
"grade": 5,
"pets": [{ "givenName": "Fluffy" }]
}
],
"address": { "state": "WA", "county": "King", "city": "Seattle" },
"creationDate": 1431620472,
"isRegistered": true
}
Step 1 - Create .NET Core 3.1 Project
Open Visual Studio 2019 and create a new project. In the search bar type ".net core" and choose "ASP .NET Core Web API" from the list and click Next
Choose a project name and solution name and click Next
Choose the target framework as .Net Core 3.1 and click Create
Step 2 - Install NuGet Packages
We have to install 3 NuGet Packages
- Microsoft.Azure.Cosmos (version 3.22.1)
- Newtonsoft.Json (version 13.0.1)
- Swashbuckle.AspNetCore (version 5.6.3)
Step 3 - Update appsettings.json file
Copy-paste the following code into the appsettings.json file.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"CosmosDb": {
"Account": "<>",
"Key": "<>",
"DatabaseName": "<>",
"ContainerName": "<>"
},
"AllowedHosts": "*"
}
Replace "Account" value with URI and "Key" with Primary Key from Azure Cosmos DB (refer to screenshots below),
Replace DatabaseName and ContainerName with Database and ID (refer to screenshots below),
Step 4 - Create the Entities
Create a new folder called Models and add 5 classes to it - Family, Parents, Children, Pets, and Address. Copy-paste the following code into respective classes.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Models
{
public class Address
{
[JsonProperty(PropertyName = "state")]
public string State { get; set; }
[JsonProperty(PropertyName = "city")]
public string City { get; set; }
[JsonProperty(PropertyName = "county")]
public string County { get; set; }
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Models
{
public class Children
{
[JsonProperty(PropertyName = "firstName")]
public string FirstName { get; set; }
[JsonProperty(PropertyName = "gender")]
public string gender { get; set; }
[JsonProperty(PropertyName = "grade")]
public int grade { get; set; }
[JsonProperty(PropertyName = "pets")]
public List<Pets> pets { get; set; }
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Models
{
public class Family
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "lastName")]
public string LastName { get; set; }
[JsonProperty(PropertyName = "children")]
public List<Children> Children { get; set; }
[JsonProperty(PropertyName = "parents")]
public List<Parents> Parents { get; set; }
[JsonProperty(PropertyName = "address")]
public Address Address { get; set; }
[JsonProperty(PropertyName = "isRegistered")]
public bool IsRegistered { get; set; }
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Models
{
public class Parents
{
[JsonProperty(PropertyName = "firstName")]
public string FirstName { get; set; }
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Models
{
public class Pets
{
[JsonProperty(PropertyName = "givenName")]
public string GivenName { get; set; }
}
}
Step 5 - Add Service
Right-click on your project in solution explorer and create a folder called "Services". Inside the Services folder create two subfolders - "Contract" and "Implementation".
Add an interface "IFamilyService" inside the Contract folder and class "FamilyService" under the Implementation folder,
Copy-paste the following code in IFamilyService Interface.
using FBAuthDemoAPI.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Services.Contract
{
public interface IFamilyService
{
Task<IEnumerable<Family>> GetFamilyDataAsync(string query);
Task AddFamilyDataAsync(Family family);
Task DeleteFamilyDataAsync(string id);
Task UpdateFamilyDataAsync(Family family);
}
}
Copy-paste the following code into FamilyService class. We have to implement the IFamilyService interface here. We have created two methods
- AddFamilyDataAsync - To add data to DB
- GetFamilyDataAsync - To retrieve data from DB
- DeleteFamilyDataAsync - To delete data from DB
- UpdateFamilyDataAsync - Update family data in DB
We have also defined a private instance of the Container class. This instance will be used to provide us with methods to do actual CRUD operations.
using FBAuthDemoAPI.Models;
using FBAuthDemoAPI.Services.Contract;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Services.Implementation
{
public class FamilyService : IFamilyService
{
private Container _container;
private static readonly JsonSerializer Serializer = new JsonSerializer();
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Id));
}
public async Task DeleteFamilyDataAsync(string id)
{
if (await GetFamilyDataFromId(id))
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey($"{id}"));
}
}
public async Task<IEnumerable<Family>> GetFamilyDataAsync(string queryString)
{
var query = this._container.GetItemQueryIterator<Family>(new QueryDefinition(queryString));
List<Family> results = new List<Family>();
while (query.HasMoreResults)
{
var response = await query.ReadNextAsync();
results.AddRange(response.ToList());
}
return results;
}
public async Task UpdateFamilyDataAsync(Family family)
{
if (await GetFamilyDataFromId(family.Id))
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Id));
}
}
private async Task<bool> GetFamilyDataFromId(string id)
{
//use parameterized query to avoid sql injection
string query = $"select * from c where c.id=@familyId";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("@familyId", id);
List<Family> familyResults = new List<Family>();
// Item stream operations do not throw exceptions for better performance.
// Use GetItemQueryStreamIterator instead of GetItemQueryIterator
//As an exercise change the Get method to use GetItemQueryStreamIterator instead of GetItemQueryIterator
FeedIterator streamResultSet = _container.GetItemQueryStreamIterator(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(id),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<Family> familyResult = streamResponse.Documents.ToObject<List<Family>>();
familyResults.AddRange(familyResult);
}
else
{
return false;
}
}
}
if (familyResults != null && familyResults.Count > 0)
{
return true;
}
return false;
}
private static T FromStream<T>(Stream stream)
{
using (stream)
{
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}
using (StreamReader sr = new StreamReader(stream))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(sr))
{
return Serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
}
}
Step 6 - Update Startup.cs
Copy-paste the following code into startup.cs class.
It's mostly a standard boilerplate code. We have added InitializeCosmosClientInstanceAsync method to create a cosmos client and inject dependencies into the service class.
We have also added swagger support.
using FBAuthDemoAPI.Services.Contract;
using FBAuthDemoAPI.Services.Implementation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using System.Linq;
using System.Threading.Tasks;
namespace FBAuthDemoAPI {
public class Startup {
/// <summary>
/// Creates a Cosmos DB database and a container with the specified partition key.
/// </summary>
/// <returns></returns>
private static async Task < FamilyService > InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection) {
string databaseName = configurationSection.GetSection("DatabaseName").Value;
string containerName = configurationSection.GetSection("ContainerName").Value;
string account = configurationSection.GetSection("Account").Value;
string key = configurationSection.GetSection("Key").Value;
Microsoft.Azure.Cosmos.CosmosClient client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
FamilyService familyService = new FamilyService(client, databaseName, containerName);
Microsoft.Azure.Cosmos.DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
return familyService;
}
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration {
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddControllers();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "FBAuthDemoApp", Version = "v1"
});
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); //This line
});
services.AddSingleton < IFamilyService > (InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "FBAuthDemoApp v1"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
}
Step 7 - Add Controller
Right-click on the Controllers folder and create a new Empty API Controller. Choose the name as FamilyController
Copy-paste the following code into the Controller class. We have written four basic methods here,
- GetFamilyData - To get all the data for all the Families in DB
- AddFamilyData - To add a family to DB
- UpdateFamilyData - To update a Family's data in DB
- DeleteFamilyData - To delete a Family from DB
using FBAuthDemoAPI.Models;
using FBAuthDemoAPI.Services.Contract;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FBAuthDemoAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FamilyController : ControllerBase
{
private readonly ILogger _logger;
private readonly IFamilyService _familyService;
public FamilyController(IFamilyService familyService, ILogger<FamilyController> logger)
{
_familyService = familyService;
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Family>>> GetFamilyData()
{
try
{
var family = await _familyService.GetFamilyDataAsync("SELECT * FROM c");
return Ok(family);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(500, "Some error occured while retreiving data");
}
}
[HttpPost]
public async Task<ActionResult> AddFamilyData(Family family)
{
try
{
if (ModelState.IsValid)
{
await _familyService.AddFamilyDataAsync(family);
}
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(500, "Some error occured while inserting data");
}
}
[HttpDelete]
public async Task<ActionResult> DeleteFamilyData(string id)
{
try
{
await _familyService.DeleteFamilyDataAsync(id);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(500, "Some error occured while deleting data");
}
}
[HttpPut]
public async Task<ActionResult> UpdateFamilyData(Family family)
{
try
{
await _familyService.UpdateFamilyDataAsync(family);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(500, "Some error occured while updating data");
}
}
}
}
Step 8 - Test your API
Switch to FBAuthDemoAPI debug profile and click F5 to start running your application.
Navigate to https://localhost:5001/swagger/index.html and swagger UI should load. You should be able to see all 4 endpoints for Get, Post, Delete, and Update.
Click on Get and click Try it out button,
Click on Execute Button,
If everything goes well you should get results similar to the screenshot below,
Try other endpoints as well to make sure they work properly.
Summary and Next Steps
In this article, we learned how to create a .NET Core 3.1 Rest API and use it to do CRUD operations on Azure Cosmos DB.
In the next article, we will publish this API project to Azure cloud and add authentication with Facebook as Identity provider.
Source Code - https://github.com/tanujgyan/FBAuthDemoAPI