Azure Functions in ASP.NET Core Web API

Azure Functions are part of Microsoft's serverless computing service, allowing developers to build and deploy event-driven applications without worrying about managing the underlying infrastructure. Here's a brief intro to Azure Functions:

What are Azure Functions?

Azure Functions are small, single-purpose, and event-triggered pieces of code that run in response to various events or triggers. They enable developers to execute code in a serverless environment without provisioning or managing servers.

Key Features:

  1. Event-driven: Functions can be triggered by various Azure services like Blob Storage, HTTP requests, timers, queues, databases, and more.
  2. Supported Languages: Azure Functions support multiple programming languages including C#, JavaScript, Python, PowerShell, and TypeScript.
  3. Scalability: Automatically scales based on demand, allowing cost optimization by paying only for resources used.
  4. Integration: Easily integrates with other Azure services and third-party services using bindings and triggers.
  5. Monitoring and Logging: Provides built-in monitoring and logging capabilities for tracking function execution and performance.

Core Concepts:

  1. Trigger: Defines how a function is invoked. Triggers can be HTTP requests, timers, queue messages, etc.
  2. Bindings: Allow functions to interact with other services. Input and output bindings simplify integration with Azure services.
  3. Bindings and Triggers Example: For instance, a function triggered by an HTTP request might use Blob Storage binding to write data to Azure Storage.

Development and Deployment:

  1. Local Development: Functions can be developed and tested locally using the Azure Functions Core Tools.
  2. Deployment: Functions can be deployed directly from Visual Studio, Azure CLI, Azure Portal, or through CI/CD pipelines.

Pricing:

Azure Functions follow a consumption-based pricing model, where you're billed based on the number of executions and resources consumed.

Use Cases:

  1. IoT: Processing sensor data or reacting to IoT events.
  2. Automation: Executing code in response to changes in databases, files, or schedules.
  3. Webhooks and APIs: Building APIs or handling HTTP requests.
  4. Data Processing: Performing data transformations, analytics, and more.

Benefits:

  1. Cost-Efficient: Pay only for the resources used during function execution.
  2. Scalability: Automatically scales based on demand.
  3. Rapid Development: Allows for quick development and deployment of code without managing infrastructure.

Azure Functions offers a versatile platform for developing and deploying small pieces of code that can respond to various events, making it an excellent choice for building serverless applications and microservices in the Azure ecosystem.

Let's build our microservice using Microsoft Azure Functions

Create New Asp.NET Core Web API Project 

First, you need to create the Asp.net Core Project using Visual Studio

Create a Data access layer 

Now add the .Net Library Project for the creation of the data access layer 

Add Model 

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model
{
    public class FetchedDataModel
    {
        public string Id { get; set; }

        public int Title { get; set; }

        public string Name { get; set; }

        public string FilePath { get; set; }
    }

}

Add Application Db Context 

using Microsoft.EntityFrameworkCore;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.ApplicationDbContext
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public DbSet<FetchedDataModel> FetchedData { get; set; }
    }
}

Add Interface for IDataFetcher

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.IRepository
{
    public interface IDataFetcher
    {
        Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log);
        Task<string> FetchDataFromAPI(string apiUrl);
        Task StoreDataInBlobStorage(FetchedDataModel fetchedDataModel);
    }
}

Implement the repository for RepositoryDataFetcher

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.IRepository;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model;
using System.Text.Json;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Repository
{
    public class RepositoryDataFetcher : IDataFetcher
    {
        private static readonly HttpClient httpClient;
        public RepositoryDataFetcher(HttpClient httpClient)
        {
            httpClient = httpClient ?? new HttpClient();
        }

        public async Task<string> FetchDataFromAPI(string apiUrl)
        {
            HttpResponseMessage response = await httpClient.GetAsync(apiUrl);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }

        public async Task StoreDataInBlobStorage(FetchedDataModel fetchedDataModel)
        {
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse("YourStorageConnectionString");
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference("data-container");
            await container.CreateIfNotExistsAsync();

            CloudBlockBlob blockBlob = container.GetBlockBlobReference($"{fetchedDataModel.Id}.json");
            using(MemoryStream ms = new MemoryStream())
            {
                JsonSerializer.SerializeAsync(ms, fetchedDataModel);
                ms.Position = 0;
                await blockBlob.UploadFromStreamAsync(ms);
            }
        }

        public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"DataFetcherFunction executed at: {DateTime.Now}");

            string apiUrl = "https://api.csharpcorner.com/Articledata"; // Replace with your API URL

            string dataJson = await FetchDataFromAPI(apiUrl);

            FetchedDataModel fetchedData = JsonSerializer.Deserialize<FetchedDataModel>(dataJson);

            await StoreDataInBlobStorage(fetchedData);
        }
    }
}

Add the Service Layer 

Add the.NET Library Project for the creation of the Service Layer 

Add the Interface for IDataFetcherService

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.IDataService
{
    public interface IDataFetcherService
    {
        Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger? log);
        Task<FetchedDataModel> FetchDataFromAPI(string apiUrl);
        Task StoreDataInBlobStorage(FetchedDataModel fetchedDataModel);
    }
}

Implement the DataFetcherService

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.IDataService;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.IRepository;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Model;
using System;
using System.Text.Json;
using System.Threading.Tasks;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.DataFetcherService
{
    public class DataFetcherService : IDataFetcherService
    {
        private readonly IDataFetcher _repositoryDataFetcher;
        private readonly ILogger _logger;

        public DataFetcherService(IDataFetcher repositoryDataFetcher, ILogger<DataFetcherService> logger)
        {
            _repositoryDataFetcher = repositoryDataFetcher;
            _logger = logger;
        }

        public async Task<FetchedDataModel> FetchDataFromAPI(string apiUrl)
        {
            try
            {
                string jsonData = await _repositoryDataFetcher.FetchDataFromAPI(apiUrl);
                if(string.IsNullOrEmpty(jsonData))
                {
                    _logger.LogWarning("Received empty data from the API.");
                    return null;
                }

                FetchedDataModel fetchedData = JsonSerializer.Deserialize<FetchedDataModel>(jsonData);
                return fetchedData;
            }
            catch(Exception ex)
            {
                _logger.LogError($"Failed to fetch data from the API: {ex.Message}");
                throw;
            }
        }


        public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer,ILogger logger)
        {
            _logger.LogInformation($"DataFetcherService executed at: {DateTime.Now}");

            string apiUrl = "https://api.csharpcorner.com/Articledata"; // Replace with your API URL

            try
            {
                FetchedDataModel fetchedData = await FetchDataFromAPI(apiUrl);

                if(fetchedData != null)
                {
                    await StoreDataInBlobStorage(fetchedData);
                }
                else
                {
                    _logger.LogWarning("Failed to fetch data from the API.");
                }
            }
            catch(Exception ex)
            {
                _logger.LogError($"Failed to run DataFetcherService: {ex.Message}");
                throw;
            }
        }


        public Task StoreDataInBlobStorage(FetchedDataModel fetchedDataModel)
        {
            try
            {
                return _repositoryDataFetcher.StoreDataInBlobStorage(fetchedDataModel);
            }
            catch(Exception ex)
            {
                _logger.LogError($"Failed to store data in Blob Storage: {ex.Message}");
                throw;
            }
        }
    }
}

Now Add the Dependency Injection 

Now add the dependencies in the IOC Container 

using MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.DataFetcherService;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.IDataService;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.ApplicationDbContext;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.IRepository;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_DAL.Repository;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;

// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IDataFetcherService, DataFetcherService>();
builder.Services.AddHttpClient<IDataFetcher, RepositoryDataFetcher>();
builder.Services.AddScoped<IDataFetcher, RepositoryDataFetcher>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Microservices Development using Microsoft Azure Functions", Version = "v1" });
});

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();

Implement the Presentation Layer 

Now Implement the Presentation layer by adding the DataFetcherController

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using MicrosoftAzureFunctionInAspNetCoreWebAPI_BAL.IDataService;

namespace MicrosoftAzureFunctionInAspNetCoreWebAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DataFetcherController : ControllerBase
    {
        private readonly IDataFetcherService _dataFetcherService;
        private readonly ILogger<DataFetcherController> _logger;

        public DataFetcherController(IDataFetcherService dataFetcherService, ILogger<DataFetcherController> logger)
        {
            _dataFetcherService = dataFetcherService;
            _logger = logger;
        }

        [HttpPost(nameof(FetchAndStoreData))]
        public async Task<IActionResult> FetchAndStoreData([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer)
        {
            try
            {
                await _dataFetcherService.Run(myTimer,null);

                return Ok("Data fetched and stored successfully!");
            }
            catch(Exception ex)
            {
                _logger.LogError($"Failed to fetch and store data: {ex.Message}");
                return StatusCode(500, "An error occurred while processing the request.");
            }
        }
    }
}

Output

Swagger API Endpoint

GitHub Project URL

https://github.com/SardarMudassarAliKhan/MicrosoftAzureFunctionInAspNetCoreWebAPI

Conclusion

Azure Functions offers a powerful solution for developers seeking to build and deploy applications in a serverless environment. With event-driven triggers, seamless integration with various Azure services, and support for multiple programming languages, they provide scalability, flexibility, and rapid development opportunities. Their ability to handle diverse use cases, from IoT to data processing and API development, underscores their versatility. Coupled with cost-efficiency and ease of monitoring, Azure Functions stand as a robust choice for creating responsive, scalable, and cost-effective applications in the Azure ecosystem.


Similar Articles