Introduction
Azure Functions is a serverless compute service and straightforward solution for running small pieces of code, or "functions," within the cloud. It can make our development even more productive. You'll write just the code you would like for the matter at hand, without fear of the whole application or the infrastructure to run it.
In this tutorial, I will try to make you understand how Azure Functions will allow us to create APIs using HTTP Trigger by using Entity Framework Core Layer as an ORM with C# Code and SQL Database for Data Storage. So, let’s get started.
You can find the Source Code here
Prerequisites
Table of Contents
- Setting up the Azure Function
- Database Schema and Entity Model
- Setting up the Connection with SQL Database by using EF Core
- Initialize Dependency Injection
- Injecting the DbContext into a function
- Implementing Functions
- Tesing the APIs using Postman
- Conclusion
Setting up the Azure Function
Open the Visual Studio and search for Blazor App. Click on the Next button,
Define the project name, path, and solution name. Click on the Create button,
After that, a new window will pop up to choose the target framework (Azure Functions v2 (.Net Core)) from the dropdown and make sure to select the Http Trigger and in the Authorization level section select Anonymous as we are not following in the present article.
This creates a skeletal project with a basic Azure Function.
Database Schema and Model Entity
Here is the schema for the employee table. Execute the schema in your respective SQL Database. Below is the picture after creating the table in the Database.
CREATE TABLE Employee(
Id int IDENTITY(1,1) PRIMARY KEY,
Name nvarchar(100) NOT NULL,
Designation nvarchar(100) NOT NULL,
City nvarchar(100) NOT NULL
);
Create a folder named Models and inside the folder will create a class named Employee - Entity Model with minimal properties to make our implementation easy
Employee. cs
namespace API_EFCore_AzureFunctions.Models
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Designation { get; set; }
public string City { get; set; }
}
}
Next, let's add the DbContext class to our application. This helps us to access the Database tables which will be generated by our Models via the application. Create a AppDbContext class to define our DbContext.
AppDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace API_EFCore_AzureFunctions.Models
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Employee> Employee { get; set; }
}
}
Setting up the Connection with SQL Database by using EF Core
In order to make a connection to SQL Database with EF Core here, we require the following packages to establish the connection by using the Db first approach. We can install the required packages by using the Nuget Package Manager or by using the Package manager console.
Make sure you install the package with version v2.0.0 because that is the fully working version without any issues with v2 Net Core.
Initialize Dependency Injection
To set up dependency injection for our function app we use the FunctionsStartup
attribute on the assembly to indicate a startup class that will run when the function app starts. In that class, which inherits from FunctionsStartup
we override the Configure
method. This allows us to retrieve the SQL connection string from configuration, and register a DbContext
in the services, which will allow us to inject the AppDbContext into our function. Create a Class named Startup.cs to integrate the Dependency Injection
Startup.cs
using API_EFCore_AzureFunctions.Models;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(API_EFCore_AzureFunctions.Startup))]
namespace API_EFCore_AzureFunctions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = "Data Source=Server Name;Integrated Security=true;Database=Database Name";
builder.Services.AddDbContext<AppDbContext>(
options => SqlServerDbContextOptionsExtensions.UseSqlServer(options, connectionString));
}
}
}
Injecting the DbContext into a Function
What dependency injection now offers to us is the ability for us to define our functions in classes that have their dependencies injected into their constructor. Open the Function1.cs file to inject our dependencies.
Function1.cs
#region Property
private readonly AppDbContext _appDbContext;
#endregion
#region Constructor
public Function1(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
#endregion
Implementing Functions
In this example, we are using five functions
- GetEmployees: Get all the employees from the DB
- CreateEmployee: Insert the employee-related information to DB
- GetEmployeebyId: Get the Employee record based on their employee Id
- UpdateEmployee: Update the existing employee with modifications
- DeleteEmployee: Deletion of Employee Record from the Database.
GetEmployees
#region Function Get Employees
/// <summary>
/// Get List of Employees
/// </summary>
/// <param name="req"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName("GetAllEmployees")]
public async Task<IActionResult> GetAllEmployees(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req, ILogger log)
{
try
{
log.LogInformation("Getting Employee list items");
return new OkObjectResult(await _appDbContext.Employee.ToListAsync());
}
catch (System.Exception)
{
throw;
}
}
#endregion
Line - 8: Added Name for the Function name attribute.
Line - 9: Defining a Function (Method)
Line - 10: Attribute Property for HTTP Trigger.
- AuthorizationLevel - Azure Function protects your HTTP trigger using Authorization keys. Authorization Level comes in three flavors
1. Anonymous: No key is required.
2. Function: A specific function key is required. This is the default value if none is specified.
3. Admin: A master key is required.
- Route - It defines the route template on which the endpoint is listening. The default value of the route is set to api/<FunctionName>.
- Method - This is used to define HTTP verbs for the functions.
Line 13- 21: Adding try catch block to handle the exceptions and fetching all the Employee data from the Database.
CreateEmployee
#region Create Employee
/// <summary>
/// Create New Employee
/// </summary>
/// <param name="req"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName("CreateEmployee")]
public async Task<IActionResult> CreateEmployee(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = Route +"/Create")]
HttpRequest req, ILogger log)
{
log.LogInformation("Creating a new employee list item");
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var input = JsonConvert.DeserializeObject<Employee>(requestBody);
var employee = new Employee { Name = input.Name, Designation = input.Designation,City= input.City };
await _appDbContext.Employee.AddAsync(employee);
await _appDbContext.SaveChangesAsync();
return new OkObjectResult(new { Message = "Record Saved SuccessFully", Data = employee });
}
#endregion
GetEmployeebyId
#region Get Employee Based on Employee Id
/// <summary>
/// Get Employee by Querying with Id
/// </summary>
/// <param name="req"></param>
/// <param name="log"></param>
/// <param name="Id"></param>
/// <returns></returns>
[FunctionName("GetEmployeebyId")]
public async Task<IActionResult> GetEmployeebyId(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "{Id}")]
HttpRequest req, ILogger log, int Id)
{
try
{
var result = await _appDbContext.Employee.FindAsync(Id);
if (result is null)
{
log.LogInformation($"Item {Id} not found");
return new NotFoundResult();
}
return new OkObjectResult(result);
}
catch (System.Exception)
{
throw;
}
}
#endregion
UpdateEmployee
#region Update Employee
/// <summary>
/// Update Employee - Changes
/// </summary>
/// <param name="req"></param>
/// <param name="log"></param>
/// <param name="Id"></param>
/// <returns></returns>
[FunctionName("UpdateEmployee")]
public async Task<IActionResult> UpdateEmployee(
[HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = Route +"/Update")]
HttpRequest req, ILogger log)
{
log.LogInformation("Updating a new employee list item");
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var updated = JsonConvert.DeserializeObject<Employee>(requestBody);
var employee = await _appDbContext.Employee.FindAsync(updated.Id);
if(employee is null)
{
log.LogError($"Item {updated.Id} not found");
return new NotFoundResult();
}
if(!string.IsNullOrEmpty(updated.Name) && !string.IsNullOrEmpty(updated.Designation))
{
employee.Name = updated.Name; employee.Designation = updated.Designation;
employee.City = updated.City;
}
_appDbContext.Employee.Update(employee);
await _appDbContext.SaveChangesAsync();
return new OkObjectResult(new { Message = "Record Updated SuccessFully", Data = employee });
}
#endregion
DeleteEmployee
#region Delete Employee
/// <summary>
/// Deletion of Employee
/// </summary>
/// <param name="req"></param>
/// <param name="log"></param>
/// <param name="Id"></param>
/// <returns></returns>
[FunctionName("DeleteEmployee")]
public async Task<IActionResult> DeleteEmployee(
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "DeleteEmployee/{Id}")]
HttpRequest req, ILogger log,int Id)
{
log.LogInformation("Updating a new employee list item");
var employee = await _appDbContext.Employee.FindAsync(Id);
if (employee is null)
{
log.LogError($"Item {Id} not found");
return new NotFoundResult();
}
_appDbContext.Employee.Remove(employee);
await _appDbContext.SaveChangesAsync();
return new OkObjectResult("Record Deleted !");
}
#endregion
Finally, we have managed to put all the code changes in place. Run the application and verify the all the methods are working as expected in the Postman. After running the application all our functions will be loaded in the terminal because the Azure Functions will use the Storage emulator to fetch the responses inside the terminal.
Funtion Run Time Version - The target framework we choosed at the time of Azure Function setup.
Now Listening on - The Port Number in which the Azure functions are running (7071).
Testing the APIs using Postman
Let's create the Employee First using the CreateEmployee API using Postman
How this actually works?
We were fetching the req body from the HTTP Trigger and attaching the Json to our Employee model class for deserialization and then passing the Employee object to the Database which you can see in the CreateEmployee Function.
Fetch the List of Employees from the GetEmployee API.
Update the Existing Record using the UpdateEmployee API and here in this object the Employee Id is mandatory. Based on Id we are fetching the existing record from the Db and updating the Same record with new object.
Delete the Record from the Database by passing the EmployeeId as Query Parameter in the route.
Conclusion
In this article, we learned how to implement the Serverless APIs using the Azure Functions and integrating with the Database by using the Entity Framework Core Dependency Injection. Thanks for staying until the end.
Thank you for reading, please let me know your questions, thoughts, or feedback in the comments section. I appreciate your feedback and encouragement.
Keep learning....!