Pages

Advertisement

Wednesday, November 8, 2023

Azure Functions: Sending Email via SendGrid

 In this blog post, I will show you how to create an Azure Functions app that can send emails via SendGrid. SendGrid is a cloud-based email service that provides reliable and scalable email delivery. Azure Functions is a serverless compute service that lets you run code without managing servers or infrastructure. By combining these two services, you can easily send emails from your Azure Functions app without worrying about the underlying email infrastructure.


To get started, you will need the following:

- An Azure account with an active subscription. If you don't have one, you can create one for free here.
- A SendGrid account with an API key. If you don't have one, you can sign up for free here and follow the instructions to create an API key.
- Visual Studio Code with the Azure Functions extension installed. You can download Visual Studio Code here and install the extension from here.
- The Azure Functions Core Tools. You can install them from here.

Once you have everything set up, follow these steps to create and deploy your Azure Functions app:

1. Open Visual Studio Code and create a new folder for your project. Name it whatever you like, such as `EmailSender`.
2. In Visual Studio Code, press `Ctrl+Shift+P` to open the command palette and select `Azure Functions: Create New Project...`. Choose the folder you created in the previous step as the location for your project.
3. Select `C#` as the language for your project and `.NET Core 3.1` as the runtime version.
4. Select `HTTP trigger` as the template for your first function and name it `SendEmail`. Choose `Anonymous` as the authorization level for your function.
5. A new file named `SendEmail.cs` will be created in your project folder with some boilerplate code for your function. Replace the code in this file with the following:

6. In Visual Studio Code, open the `local.settings.json` file in your project folder and add a new setting named `SENDGRID_API_KEY` with the value of your SendGrid API key. This file is used to store app settings for local development and testing. Make sure not to commit this file to source control as it contains sensitive information.
7. In Visual Studio Code, press `F5` to run your function app locally. You should see a message in the terminal window that says `Http Functions: SendEmail: [POST] http://localhost:7071/api/SendEmail`.
8. To test your function, you can use a tool like Postman or curl to send a POST request to the function URL with a JSON body that contains the email parameters. For example, you can use the following curl command:

//csharp
using System;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace EmailSender
{
    public static class SendEmail
    {
        [FunctionName("SendEmail")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            // Parse the request body
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);

            // Get the email parameters from the request body
            string fromEmail = data?.fromEmail;
            string fromName = data?.fromName;
            string toEmail = data?.toEmail;
            string toName = data?.toName;
            string subject = data?.subject;
            string body = data?.body;

            // Validate the email parameters
            if (string.IsNullOrEmpty(fromEmail) || string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(subject) || string.IsNullOrEmpty(body))
            {
                return new BadRequestObjectResult("Please provide valid email parameters in the request body.");
            }

            try
            {
                // Create a SendGrid client with your API key
                var apiKey = Environment.GetEnvironmentVariable("SENDGRID_API_KEY");
                var client = new SendGridClient(apiKey);

                // Create a SendGrid message object with the email parameters
                var from = new EmailAddress(fromEmail, fromName);
                var to = new EmailAddress(toEmail, toName);
                var msg = MailHelper.CreateSingleEmail(from, to, subject, body, body);

                // Send the email using the SendGrid client
                var response = await client.SendEmailAsync(msg);

                // Check the response status code and return accordingly
                if (response.StatusCode == HttpStatusCode.Accepted)
                {
                    return new OkObjectResult("Email sent successfully.");
                }
                else
                {
                    return new BadRequestObjectResult($"Email failed to send. Status code: {response.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                // Log and return any exception that occurs
                log.LogError(ex, "An error occurred while sending email.");
                return new StatusCodeResult(StatusCodes.Status500InternalServerError);
            }
        }
    }
}


Best Practices of Software Development in C#

Software development is a complex and creative process that requires careful planning, design, implementation, testing, and maintenance. Software developers need to follow some best practices to ensure that their code is consistent, readable, understandable, and maintainable. In this article, we will discuss some of the best practices of software development in C#, a popular and powerful programming language.

1. Follow Consistent Naming Conventions

In C#, it is important to follow consistent naming conventions for variables, methods, classes, interfaces, and other elements. Naming conventions help to avoid naming conflicts, improve code readability, and make it easier for developers to understand and navigate the code. Some of the common naming conventions in C# are:

  • Use Pascal case for class names, struct names, method names, property names, constant field names, and interface names. Pascal case means that the first letter of each word in the name is capitalized, and there are no underscores or hyphens between words. For example: Customer, OrderService, GetTotalPrice, FirstName, MaxValue, IComparable.
  • Use camel case for parameter names, local variable names, and private field names. Camel case means that the first letter of the first word in the name is lowercase, and the first letter of each subsequent word is capitalized. There are no underscores or hyphens between words. For example: firstName, orderService, totalPrice, maxValue, _customer.
  • Use underscore prefix for private field names. This helps to distinguish them from local variables and parameters, and to avoid naming collisions with property names. For example: _firstName, _orderService, _totalPrice, _maxValue.
  • Use descriptive and meaningful names for variables, methods, classes, and other elements. Avoid using vague, generic, or abbreviated names that do not convey the purpose or functionality of the element. For example: Customer, OrderService, GetTotalPrice, FirstName, MaxValue are better than C, OS, GTP, FN, MV.
  • Use singular names for classes, structs, enums, and interfaces. Use plural names for collections, arrays, and lists. For example: Customer, Order, Product, Customers, Orders, Products.
  • Use I prefix for interface names. This helps to identify them as interfaces, and to avoid naming conflicts with classes that implement them. For example: IComparable, IDisposable, IEnumerable.
  • Use generic type parameters that start with T, followed by a descriptive name. For example: TKey, TValue, TEntity, TResult.

2. Use Proper Code Organization

Code organization is another important aspect of software development in C#. Code organization refers to how the code is structured, grouped, and formatted in a logical and consistent way. Code organization helps to improve code readability, maintainability, and modularity. Some of the best practices for code organization in C# are:

  • Use namespaces to group related classes, structs, enums, interfaces, and delegates. Namespaces help to avoid naming conflicts, and to provide a logical hierarchy for the code. For example: System, System.IO, System.Collections.Generic, System.Linq.
  • Use access modifiers to specify the visibility and accessibility of classes, methods, properties, fields, and other elements. Access modifiers help to control the scope and encapsulation of the code, and to prevent unauthorized or unintended access. For example: public, private, protected, internal, protected internal, private protected.
  • Use regions to group related code blocks within a class or a method. Regions help to collapse and expand the code, and to provide a descriptive label for the code. For example: #region Fields, #region Properties, #region Constructors, #region Methods, #endregion.
  • Use indentation to align the code blocks and statements within a class or a method. Indentation helps to show the structure and hierarchy of the code, and to improve code readability. For example: use four spaces or one tab for each level of indentation.
  • Use whitespace to separate the code blocks and statements within a class or a method. Whitespace helps to create a clear and consistent layout for the code, and to improve code readability. For example: use one blank line between methods, properties, fields, and other elements; use one space before and after operators, commas, semicolons, and parentheses; use one space after keywords, such as if, for, while, switch, catch.
  • Use comments to explain the purpose, functionality, logic, or algorithm of the code. Comments help to document the code, and to make it easier for developers to understand and maintain the code. For example: use // for single-line comments, and /* */ for multi-line comments.

3. Avoid Magic Numbers and Strings

Magic numbers and strings are literal values that are hard-coded in the code, and that have no obvious meaning or significance. Magic numbers and strings make the code difficult to understand, maintain, and modify. They also introduce potential errors and bugs, and reduce the flexibility and reusability of the code. Some of the best practices to avoid magic numbers and strings in C# are:

  • Use constants to declare and assign meaningful names to magic numbers and strings. Constants are immutable values that can be used throughout the code, and that can be easily changed in one place if needed. For example: const int MaxLength = 10;, const string ConnectionString = \"Data Source=localhost;Initial Catalog=TestDB;Integrated Security=True\";.
  • Use enumerations to declare and assign meaningful names to a set of related magic numbers. Enumerations are named constants that represent a group of values, and that can be used to improve code readability and type safety. For example: enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };, enum Color { Red, Green, Blue, Yellow, Black, White };.
  • Use configuration files to store and retrieve magic numbers and strings that are related to the application settings, such as connection strings, app settings, user preferences, etc. Configuration files are external files that can be easily edited and updated without recompiling the code, and that can be used to provide different values for different environments, such as development, testing, and production. For example: appsettings.json, web.config, app.config.

4. Handle Exceptions Properly

Exceptions are unexpected or abnormal events that occur during the execution of the code, and that disrupt the normal flow of the program. Exceptions can be caused by various reasons, such as invalid input, network failure, file not found, division by zero, etc. Exceptions can result in undesirable outcomes, such as data loss, memory leak, security breach, or application crash. Therefore, it is essential to handle exceptions properly in C#. Some of the best practices for exception handling in C# are:

  • Use try-catch-finally blocks to enclose the code that may throw an exception, and to provide appropriate actions for handling the exception. The try block contains the code that may cause an exception; the catch block contains the code that executes when an exception occurs; the finally block contains the code that always executes, regardless of whether an exception occurs or not. For example:
try { //code that may throw an exception } catch (Exception ex) { //code that handles the exception } finally { //code that always executes }
  • Use specific exception types to catch and handle different kinds of exceptions. Specific exception types help to provide more information and context about the exception, and to handle the exception more appropriately and accurately. For example: FileNotFoundException, InvalidOperationException, ArgumentNullException, DivideByZeroException.
  • Use multiple catch blocks to catch and handle different kinds of exceptions separately. Multiple catch blocks help to provide different actions for different exceptions, and to handle the exceptions in the order of specificity. For example:
try { //code that may throw an exception } catch (FileNotFoundException ex) { //code that handles file not found exception } catch (DivideByZeroException ex) { //code that handles division by zero exception } catch (Exception ex) { //code that handles any other exception }
  • Use throw keyword to rethrow an exception, or to throw a new exception. The throw keyword helps to propagate the exception to the caller, or to create a custom exception with a specific message or inner exception. For example:
try { //code that may throw an exception } catch (Exception ex) { //code that handles the exception throw; //rethrow the exception //or throw new CustomException(\"Custom message\", ex); //throw a new exception }
  • Use finally block to release any resources that are acquired in the try block, such as files, streams, sockets, database connections, etc. The finally block helps to ensure that the resources are properly disposed and freed, regardless of whether an exception occurs or not. For example:
FileStream fs = null; try { fs = new FileStream(\"test.txt\", FileMode.Open); //code that may throw an exception } catch (Exception ex) { //code that handles the exception