Introduction
In this article, we will learn the following things
- Create a .NET 6 REST API using VS 2022
- Create Unit Test
- Create an Azure pipeline for CI/CD process
- Deploy to Azure Cloud
Prerequisites
- Azure account - If you don't have it already you can create one for free by visiting Cloud Computing Services | Microsoft Azure.
- Github Account
- Azure DevOps Account
- Visual Studio 2022
- SQL Server Management Studio
Create a .NET 6 REST API
In this section, we will create a simple .NET 6 REST API that can perform CRUD operations on a SQL Server on Azure Cloud.
Create a New Project
1. Open VS 2022 and choose ASP .NET Core Web API project and click Next

2. Give a meaningful name for your project and click Next

3. Choose the framework as .NET 6 and click create

4. This will create a new project for you with a default controller and a model class. Since we will be creating our own controller and Model classes let's start by deleting the default files. Open solution Explorer and delete the following files - WeatherForecastController.cs, and WeatherForecast.cs

Add Model and DbContext class
1. In the Solution Explorer right-click on your project and add a new folder called Model, add two subfolders DAL and Entities inside Model and also add a new class called BookDBContext. Inside DAL add two subfolders called Contract and Implementation.

2. In the Entities folder add a new class Book. This class will be used by EF-core to build the DB table using the code-first approach. Copy-paste the following code into Book.cs file
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BooksNET6API.Models.Entities
{
public class Book
{
public Book()
{
}
[Key]
public int Id { get; set; }
[Column(TypeName = "nvarchar(100)")]
[Required]
public string Name { get; set; }
[Column(TypeName = "nvarchar(50)")]
[Required]
public string Genere { get; set; }
[Column(TypeName = "nvarchar(50)")]
[Required]
public string PublisherName { get; set; }
}
}
3. Install the following NuGet packages - Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Tools
4. Copy-paste the following code into BookDBContext class
using BooksNET6API.Models.Entities;
using Microsoft.EntityFrameworkCore;
namespace BooksNET6API.Models
{
public class BookDBContext: DbContext
{
public DbSet<Book> Books { get; set; }
public BookDBContext(DbContextOptions<BookDBContext> options) : base(options)
{
}
}
}
5. We have to add a connection string. For this article, we will be using Microsoft SQL Server local DB. Open the appsettings.json file and add the connection string
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=BookDBRestAPI;Integrated Security=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
6. Configure the connection string in Program.cs class. Unlike .NET 5 or .NET 3.1, we don't have a Startup class anymore. Both Startup and Program classes are merged into one. Open program.cs and copy-paste the following code. We have added line 6 and line 7 which pulls the connection string from the config file and registers the DbContext with services.
using BooksNET6API.Models;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
string connString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<BookDBContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Add Migrations
1. The next step is to add migrations and create the DB table using EF Core. Go to Tools-> NuGet Package Manager -> Package Manager Console and run the following commands
- add-migration AddBooktoDB
- update-database
2. Go to SQL Server and check if a database named BookDBRestAPI is created or not. Inside the database, there should be a table named Books.

Add a new Controller
1. In solution explorer, right-click on Controllers folder -> Add -> Controller...

2. Choose API-> API Controller - Empty and click Add

3. Copy-paste the following code into BooksController class
#nullable disable
using BooksNET6API.Models;
using BooksNET6API.Models.DAL.Contract;
using BooksNET6API.Models.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BooksNET6API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly IBookRepo bookRepo;
public BooksController(IBookRepo _bookRepo)
{
bookRepo = _bookRepo;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Book>>> GetBooks()
{
try
{
return await bookRepo.GetBooks();
}
catch
{
return StatusCode(500);
}
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetBook(int id)
{
try
{
var book = await bookRepo.GetBook(id);
return book;
}
catch
{
return StatusCode(500);
}
}
[HttpPut("{id}")]
public async Task<IActionResult> PutBook(int id, Book book)
{
try
{
var result = await bookRepo.PutBook(id, book);
return result;
}
catch(DbUpdateConcurrencyException ex)
{
return StatusCode(409);
}
catch (Exception ex)
{
return StatusCode(500);
}
}
[HttpPost]
public async Task<ActionResult<Book>> PostBook(Book book)
{
try
{
var result = await bookRepo.PostBook(book);
return CreatedAtAction("GetBook", new { id = result.Value.Id }, book);
}
catch
{
return StatusCode(500);
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteBook(int id)
{
try
{
var result = await bookRepo.DeleteBook(id);
return result;
}
catch
{
return StatusCode(500);
}
}
}
}
Create Repository Class
We will be following the Repository Design pattern.
1. Create an interface IBookRepo in Models->DAL->Contract folder. Copy-paste the following code into IBookRepo
using BooksNET6API.Models.Entities;
using Microsoft.AspNetCore.Mvc;
namespace BooksNET6API.Models.DAL.Contract
{
public interface IBookRepo
{
Task<ActionResult<IEnumerable<Book>>> GetBooks();
Task<ActionResult<Book>> GetBook(int id);
Task<IActionResult> PutBook(int id, Book book);
Task<ActionResult<Book>> PostBook(Book book);
Task<IActionResult> DeleteBook(int id);
}
}
2. Create a class BookRepo in Models->DAL->Implementation folder. Copy-paste the following code
using BooksNET6API.Models.DAL.Contract;
using BooksNET6API.Models.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BooksNET6API.Models.DAL.Implementation
{
public class BookRepo:IBookRepo
{
private readonly BookDBContext bookDBContext;
public BookRepo(BookDBContext _bookDBContext)
{
bookDBContext = _bookDBContext;
}
public async Task<IActionResult> DeleteBook(int id)
{
var book = await bookDBContext.Books.FindAsync(id);
if (book == null)
{
return new NotFoundResult();
}
bookDBContext.Books.Remove(book);
await bookDBContext.SaveChangesAsync();
return new NoContentResult();
}
public async Task<ActionResult<Book>> GetBook(int id)
{
var book = await bookDBContext.Books.FindAsync(id);
if (book == null)
{
return new NotFoundResult();
}
return book;
}
public async Task<ActionResult<IEnumerable<Book>>> GetBooks()
{
return await bookDBContext.Books.ToListAsync();
}
public async Task<ActionResult<Book>> PostBook(Book book)
{
bookDBContext.Books.Add(book);
await bookDBContext.SaveChangesAsync();
return book;
}
public async Task<IActionResult> PutBook(int id, Book book)
{
if (id != book.Id)
{
return new BadRequestResult();
}
bookDBContext.Entry(book).State = EntityState.Modified;
try
{
await bookDBContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BookExists(id))
{
return new NotFoundResult();
}
else
{
throw;
}
}
return new NoContentResult();
}
private bool BookExists(int id)
{
return bookDBContext.Books.Any(e => e.Id == id);
}
}
}
3. Add the following code to Program.cs
builder.Services.AddScoped<IBookRepo, BookRepo>();
Create Unit Test using xUnit and Moq
1. Right click on solution -> Add -> New Project
2. Choose xUnit Test project. Give it a proper name and choose the framework as .NET 6

3. Install the NuGet package - Moq v 4.16.1, FluentAssertions v 6.4.0
4. Copy-paste the following code in your unit test class
using BooksNET6API.Controllers;
using BooksNET6API.Models;
using BooksNET6API.Models.DAL.Contract;
using BooksNET6API.Models.Entities;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq;
using System;
using System.Threading.Tasks;
using Xunit;
namespace BookRestAPIXUnitTest
{
public class BookRestAPIUnitTest
{
private BooksController booksController;
private int Id = 1;
private readonly Mock<IBookRepo> bookStub = new Mock<IBookRepo>();
Book sampleBook = new Book
{
Id = 1,
Name = "State Patsy",
Genere = "Action/Adventure",
PublisherName = "Queens",
};
Book toBePostedBook = new Book
{
Name = "Federal Matters",
Genere = "Suspense",
PublisherName = "Harpers",
};
[Fact]
public async Task GetBook_BasedOnId_WithNotExistingBook_ReturnNotFound()
{
//Arrange
//use the mock to set up the test. we are basically telling here that whatever int id we pass to this method
//it will always return null
booksController = new BooksController(bookStub.Object);
bookStub.Setup(repo => repo.GetBook(It.IsAny<int>())).ReturnsAsync(new NotFoundResult());
//Act
var actionResult = await booksController.GetBook(1);
//Assert
Assert.IsType<NotFoundResult>(actionResult.Result);
}
[Fact]
public async Task GetBook_BasedOnId_WithExistingBook_ReturnBook()
{
//Arrange
//use the mock to set up the test. we are basically telling here that whatever int id we pass to this method
//it will always return a new Book object
bookStub.Setup(service => service.GetBook(It.IsAny<int>())).ReturnsAsync(sampleBook);
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.GetBook(1);
//Assert
Assert.IsType<Book>(actionResult.Value);
var result = actionResult.Value;
//Compare the result member by member
sampleBook.Should().BeEquivalentTo(result,
options => options.ComparingByMembers<Book>());
}
[Fact]
public async Task PostVideoGame_WithNewVideogame_ReturnNewlyCreatedVideogame()
{
//Arrange
bookStub.Setup(repo => repo.PostBook(It.IsAny<Book>())).ReturnsAsync(sampleBook);
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.PostBook(toBePostedBook);
//Assert
Assert.Equal("201", ((CreatedAtActionResult)actionResult.Result).StatusCode.ToString());
}
[Fact]
public async Task PostVideoGame_WithException_ReturnsInternalServerError()
{
//Arrange
bookStub.Setup(service => service.PostBook(It.IsAny<Book>())).Throws(new Exception());
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.PostBook(null);
//Assert
Assert.Equal("500", ((StatusCodeResult)actionResult.Result).StatusCode.ToString());
}
[Fact]
public async Task PutVideoGame_WithException_ReturnsConcurrencyExecption()
{
//Arrange
bookStub.Setup(service => service.PutBook(It.IsAny<int>(), It.IsAny<Book>())).Throws(new DbUpdateConcurrencyException());
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.PutBook(Id, sampleBook);
//Assert
Assert.Equal("409", ((StatusCodeResult)actionResult).StatusCode.ToString());
}
[Fact]
public async Task PutVideoGame_WithException_ReturnsExecption()
{
//Arrange
bookStub.Setup(service => service.PutBook(It.IsAny<int>(), It.IsAny<Book>())).Throws(new Exception());
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.PutBook(Id, sampleBook);
//Assert
Assert.Equal("500", ((StatusCodeResult)actionResult).StatusCode.ToString());
}
[Fact]
public async Task PutVideoGame_WithExistingVideogame_BasedOnId_ReturnUpdatedVideogame()
{
//Arrange
bookStub.Setup(service => service.PutBook(It.IsAny<int>(), It.IsAny<Book>())).ReturnsAsync(new NoContentResult());
booksController = new BooksController(bookStub.Object);
//Act
var actionResult = await booksController.PutBook(Id, sampleBook);
//Assert
actionResult.Should().BeOfType<NoContentResult>();
}
}
}




































A ZPosted Jul 17, 2023, 10:02 AM
After following all the steps I managed to complete everything but at the end of the deployment, my API APP URL is not working. Can you please help me whether I am missing any steps? @tanuj-gyan
Md Ashiqur RahmanPosted Sep 28, 2022, 10:53 PM
Thanks Tanuj