Configure CI/CD pipeline with Jenkins and Github using an asp.net core application
Overview
Jenkins is an open source automation server to facilitate CI/CD. It helps to automate software development jobs like building, testing, and deploying. In this article we are going to show you how to configure CI/CD pipeline with Jenkins and Github for an ASP.NET Core Web API application.
Tools and Technology used
The following tools and technologies are used to configure CI/CD
- Visual Studio 2022
- Visual C#
- ASP.NET Core Web API
- Jenkins
- Github
- IIS
Let’s configure Jenkins for an asp.net core web api applications.
Step 1: Download and install Java
Jenkin is developed by Java. So it needs Java runtime to run. Download and install latest version of Java SDK from here
Step 2: Download Jenkins
- Download Jenkins from here
- Click Download -> Download Jenkins 2.332.1 LTS for: -> Windows
Step 3: Install Jenkins on Windows
- Double click on Jenkins.msi file and Click Next
- Choose Logon Type (I have choosen Run Service as LocalSystem) and clik next.
- Type a port number or keep default 8080 (I have choosen 8080). Click Test port and Next.
- Once the installation is done. Navigate to the URL http://localhost:8080/ . You will get an screen mentioned Unlock Jenkins.
- Go to the file mentioned in the screen and copy and pest the password on Andminstrator Password text box and click continue.
- Now you will get the following screen and click on Install suggested plugins.
- Wait untill all plugins are installed. If any plugins installation is failed, try again. Once installation is done, you will see the following screen.
- Fill up the form, type user name and passwrod, and click Save & Continue.
- You will get the another screen, clik Save & Finish.
- Now our jenkins is ready. Click Start using Jenkins.
Step 4: To avoid a login each time do the following
- Navigate Manage Jenkins -> Configure Global Security
- Mark “Allow anonymous read access” as below
Step 5: Install some custom plugins
- Navigate to “Manage Jenkins” -> “Manage Plugins”
- Click “Available” tab
- Install MSBuild, MSTest, MSTestRunner, PowerShell, VSTestRunner and Git plugin if those are not already installed.
- You can use search field to find out the plugins.
- Now click Install without restart.
Step 6: Create an asp.net core web api project
Step 6.1: Create an Web api project
- Create a ASP.NET Core Web API Project Name HRM.API. Keep Solution name as HRM.
Step 6.2: Install the nuget packages
- Install the following nuget packages on HRM.API project.
PM> Install-Package Microsoft.EntityFrameworkCore 6.0.3
PM> Install-Package Microsoft.EntityFrameworkCore.InMemory 6.0.3
Step 6.3: Create model class
- Create a model class name - Employee in Models folder.
Employee.cs
namespace HRM.API.Models
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Designation { get; set; }
public string FathersName { get; set; }
public string MothersName { get; set; }
public DateTime DateOfBirth { get; set; }
}
}
Step 6.4: Create Context and Seed data generator class in Db folder
HRMContext.cs
using HRM.API.Models;
using Microsoft.EntityFrameworkCore;
namespace HRM.API.Db
{
public class HRMContext : DbContext
{
public HRMContext(DbContextOptions<HRMContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
}
}
SeedDataGenerator.cs
using HRM.API.Models;
using Microsoft.EntityFrameworkCore;
namespace HRM.API.Db
{
public class SeedDataGenerator
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new HRMContext(
serviceProvider.GetRequiredService<DbContextOptions<HRMContext>>()))
{
// Check any employee exists
if (context.Employees.Any())
{
return; // Data already exists no need to generate
}
context.Employees.AddRange(
new Employee
{
Name = "Md. Mahedee Hasan",
Designation = "Head of Software Development",
FathersName = "Yeasin Bhuiyan",
MothersName = "Moriom Begum",
DateOfBirth = new DateTime(1984, 12, 19, 00, 00, 00)
},
new Employee
{
Name = "Khaleda Islam",
Designation = "Software Engineer",
FathersName = "Shahidul Islam",
MothersName = "Momtaz Begum",
DateOfBirth = new DateTime(1990, 10, 29, 00, 00, 00)
},
new Employee
{
Name = "Tahiya Hasan Arisha",
Designation = "Jr. Software Engineer",
FathersName = "Md. Mahedee Hasan",
MothersName = "Khaleda Islam",
DateOfBirth = new DateTime(2017, 09, 17, 00, 00, 00)
},
new Employee
{
Name = "Humaira Hasan",
Designation = "Jr. Software Engineer",
FathersName = "Md. Mahedee Hasan",
MothersName = "Khaleda Islam",
DateOfBirth = new DateTime(2021, 03, 17, 00, 00, 00)
}
);
context.SaveChanges();
}
}
}
}
Step 6.5:Create Repository and It’s Interface in Repository folder
IEmployeeRepository.cs
using HRM.API.Models;
namespace HRM.API.Repository
{
public interface IEmployeeRepository
{
public Task<IEnumerable<Employee>> SelectAllEmployees();
public Task<Employee> SelectEmployee(int id);
public Task<string> UpdateEmployee(int id, Employee employee);
public Task<string> SaveEmployee(Employee employee);
public Task<string> DeleteEmployee(int id);
}
}
EmployeeRepository.cs
using HRM.API.Db;
using HRM.API.Models;
using Microsoft.EntityFrameworkCore;
namespace HRM.API.Repository
{
public class EmployeeRepository : IEmployeeRepository
{
private readonly HRMContext _context;
public EmployeeRepository(HRMContext context)
{
_context = context;
}
public async Task<IEnumerable<Employee>> SelectAllEmployees()
{
try
{
var allemployess = _context.Employees.ToListAsync();
return await allemployess;
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<Employee> SelectEmployee(int id)
{
try
{
var employee = _context.Employees.FindAsync(id);
return await employee;
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<string> UpdateEmployee(int id, Employee employee)
{
if (id != employee.Id)
{
return "Cannot be updated!";
}
_context.Entry(employee).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
return "Data updated successfully!";
}
catch (DbUpdateConcurrencyException exp)
{
if (!EmployeeExists(id))
{
return "Data not found!";
}
else
{
throw (exp);
}
}
}
public async Task<string> SaveEmployee(Employee employee)
{
_context.Employees.Add(employee);
try
{
await _context.SaveChangesAsync();
return "Data saved successfully!";
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<string> DeleteEmployee(int id)
{
var employee = await _context.Employees.FindAsync(id);
if (employee == null)
{
return "Data not found!";
}
_context.Employees.Remove(employee);
await _context.SaveChangesAsync();
return "Data deleted successfully!";
}
private bool EmployeeExists(int id)
{
return _context.Employees.Any(e => e.Id == id);
}
}
}
Step 6.6: Create Employee Service class and it’s interface in Services folder
IEmployeeService
using HRM.API.Models;
namespace HRM.API.Services
{
public interface IEmployeeService
{
public Task<IEnumerable<Employee>> GetEmployees();
public Task<Employee> GetEmployee(int id);
public Task<string> EditEmployee(int id, Employee employee);
public Task<string> AddEmployee(Employee employee);
public Task<string> RemoveEmployee(int id);
}
}
EmployeeService.cs
using HRM.API.Models;
using HRM.API.Repository;
namespace HRM.API.Services
{
public class EmployeeService : IEmployeeService
{
IEmployeeRepository _employeeRepository;
public EmployeeService(IEmployeeRepository repository)
{
_employeeRepository = repository;
}
public async Task<IEnumerable<Employee>> GetEmployees()
{
try
{
return await _employeeRepository.SelectAllEmployees();
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<Employee> GetEmployee(int id)
{
try
{
return await _employeeRepository.SelectEmployee(id);
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<string> EditEmployee(int id, Employee employee)
{
try
{
return await _employeeRepository.UpdateEmployee(id, employee);
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<string> AddEmployee(Employee employee)
{
try
{
return await _employeeRepository.SaveEmployee(employee);
}
catch (Exception exp)
{
throw (exp);
}
}
public async Task<string> RemoveEmployee(int id)
{
try
{
return await _employeeRepository.DeleteEmployee(id);
}
catch (Exception exp)
{
throw (exp);
}
}
}
}
Step 6.7: Modify Program.cs file
- Modify Program.cs file for InMemory database and Enable Swagger for both dev and release.
Program.cs
using HRM.API.Db;
using HRM.API.Repository;
using HRM.API.Services;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Configure in memory database
builder.Services.AddDbContext<HRMContext>(opt => opt.UseInMemoryDatabase("HRMDB"));
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//Register DI
builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();
builder.Services.AddScoped<IEmployeeService, EmployeeService>();
var app = builder.Build();
// 2. Find the service within the scope to use
using (var scope = app.Services.CreateScope())
{
// 3. Get the instance of HRMContext in our service layer
var services = scope.ServiceProvider;
var context = services.GetRequiredService<HRMContext>();
// 4. Call the SeedDataGenerator to generate seed data
SeedDataGenerator.Initialize(services);
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
//app.UseSwagger();
//app.UseSwaggerUI();
}
// To show both development and deployment
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 7: Create a publish profile on our application
- Click right button on the HRM.API Project -> Click Publish.
- Click ‘+’ sign to create a profile.
- Select Folder and click Next.
- Release location is like - bin\Release\net6.0\publish\
- Click Finish
- From “more action” rename the profile as - JenkinsProfile
-
You will see two folders are created in Properties -> PublishProfiles folder. Their name is - JenkinsProfile.pubxml and JenkinsProfile.pubxml.user
- Modify the pubxml file in Properties/PublishProfiles/JnekinsProfile.pubxml
- Change WebPublishMethod to Package from FileSystem as like below.
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net6.0\publish\</PublishUrl>
<WebPublishMethod>Package</WebPublishMethod>
</PropertyGroup>
</Project>
- Commit and push this changes on the github.
Step 8: Create a test project name HRM.Test on the same solution
- Add necessary test cases for HRM.API
Step 9: Build and Run Unit Test cases localy
- To build the application locally. Go to the directory of the HRM.API applycation where solution file exists.
- Run the following command on terminal to build the application.
PS D:\Projects\Github\code-sample02\jenkins-demo\HRM> dotnet build .\HRM.sln
- To Run unit test locally go the directory where test project file exists and run the following command.
PS D:\Projects\Github\code-sample02\jenkins-demo\HRM\HRM.Test> dotnet test .\HRM.Test.csproj
Step 10: Install Microsoft Web Deploy
- Download and install latest version of Microsoft web deploy, if it is not already installed. In my case, it is already installed with visual studio 2022.
Step 11: Create Credentials Id
- Navigate to “Manage Jenkins” -> Manage Credentials -> Click Jenkins -> Click Global Credentials (Unrestricted) -> Click Adding some credentials.
- In this case, I have given my Github user name and password.
- Fill the form and click OK
- Id will be generated automatically.
- Back to Global credentials (unrestricted) and click Update. You will get ID here.
Step 12: Create Pipeline using Jenkins
- Click “Create a job” on the Jenkins home page.
- Select Pipeline of the next screen, give a name of the pipeline, I am giving the name of pipeline is “HRMPipelines” and Click Ok.
- Now add follwoing script on Pipleline section as mentioned below.
pipeline {
agent any
environment {
dotnet = 'C:\\Program Files\\dotnet\\dotnet.exe'
}
stages {
stage('Checkout') {
steps {
git credentialsId: 'aee7d65a-33a7-4616-ab5e-4a1598289b1b', url: 'https://github.com/mahedee/code-sample02.git', branch: 'main'
}
}
stage('Build') {
steps {
bat 'dotnet build %WORKSPACE%\\jenkins-demo\\HRM\\HRM.sln --configuration Release'
//bat 'dotnet build C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\HRMPipelines\\jenkins-demo\\HRM\\HRM.sln --configuration Release'
}
}
stage('Test') {
steps {
bat 'dotnet test %WORKSPACE%\\jenkins-demo\\HRM\\HRM.Test\\HRM.Test.csproj --logger:trx'
}
}
stage("Release"){
steps {
bat 'dotnet build %WORKSPACE%\\jenkins-demo\\HRM\\HRM.sln /p:PublishProfile=" %WORKSPACE%\\jenkins-demo\\HRM\\HRM.API\\Properties\\PublishProfiles\\JenkinsProfile.pubxml" /p:Platform="Any CPU" /p:DeployOnBuild=true /m'
}
}
stage('Deploy') {
steps {
// Stop IIS
bat 'net stop "w3svc"'
// Deploy package to IIS
bat '"C:\\Program Files (x86)\\IIS\\Microsoft Web Deploy V3\\msdeploy.exe" -verb:sync -source:package="%WORKSPACE%\\jenkins-demo\\HRM\\HRM.API\\bin\\Debug\\net6.0\\HRM.API.zip" -dest:auto -setParam:"IIS Web Application Name"="HRM.Web" -skip:objectName=filePath,absolutePath=".\\\\PackageTmp\\\\Web.config$" -enableRule:DoNotDelete -allowUntrusted=true'
// Start IIS again
bat 'net start "w3svc"'
}
}
}
}
This is the sequential script for the pipeline to execute the stages one by one. Here we used 5 steps - Checkout, Build, Test, Release, Deploy
Stage 1 - Checkout: In this stage, we provide the URL and Git repository, branch as main and git credentials id. Which I have created on step 10. I pull the source code to workspace. Default workspace is - C:\ProgramData\Jenkins.jenkins\workspace
Stage 2 - Build: In this stage, I have build the specific project. Here HRM.API. Keep in mind you have to direct the location where sln file exists.
Stage 3 - Test : In this stage, I have run test project providing test project link.
Stage 4 - Release: In this stage, A release package HRM.zip is created in the mentioned location which is provided on JenkinsProfile.pubxml. You may check each steps output using console after executing pipeline.
Stage 5 - Deploy: In this tage, first stop IIS. Then deploy package to IIS and then Start IIS again. So, I have used three bat command here.
Step 13: Configure Build Triggers
- Navigate to HRMPipelines -> Build Triggers
- Mark GitHub hook trigger for GITScm polling and Pool SCM
- Type schedule as * * * * * which means, keep checking the Git repository and as soon as commit/check-in is done, trigger the build process. Note: there is a space after each star.
-
You can use */15 * * * * if you want to run pipeline after 15 minutes of push on main.
-
Click Save
Step 14: Run pipeline manually
- Go to the dashboard
- Click HRMPipelines
- Click Build Now and you will see the following screen. It means, build and deployment completed sucessfully.
Step 15: Change application and see output
- Change a little bit on your application
- Commit and push it to the Github
- Wait a bit and you will see the build and deployed successfully.
Now test your application using the following URL:
http://localhost:8012/swagger/index.html
- Here I have hosted the application on 8012 port on IIS.