European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET Core Hosting - HostForLIFE :: SOLID With .Net Core

clock March 28, 2023 07:25 by author Peter

This article will explain the SOLID principles, which one of those acronyms means alongside a practical example of an architectural problem that we could use in order to fix the problem.


What are the SOLID principles?
A SOLID principle is a group of 5 different design patterns whose main focus is to create loosely coupled architectures with a high level of maintainability. The name SOLID comes from the acronym of its five principles,
    S - Single Responsability Principle;
    O - Open-Closed Principle;
    L - Liskov Substitution Principle;
    I - Interface Segregation Principle;
    D - Dependency Inversion Principle.

If in your project you have the common problem to solve a bug and create two different bugs then you must start to think about using the SOLID principles in order to have a cleaner and more organized code, which does not break other features when you are working in a specific feature.

S - SRP - Single Responsibility Principle
"A class should have one, and only one, a reason to change."  (Robert C. Martin)

Avoiding Jack of all Trades' classes, the Single Responsibility Principle says that your class should have a single responsibility. This means that you should avoid, at all costs, having classes with multiple functionalities.

Imagine doing maintenance in a class with multiples functionalities, the risk of fixing one functionality but breaking other functionalities as a side-effect is extremely high. To overcome those kinds of situations you should apply the Single Responsibility Principle.

One great example of the Single Responsibility Principle being applied is the 3-tier projects architectures, where each tier is responsible only for their tasks. Ie.: the business layer is responsible to validate business logics where the repository layer is responsible to manipulate the database.


 

O - OCP - Open/Closed Principle
"A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defined, there is no need to change the original or to disturb its clients." (Bertrand Meyer)

The topic here is also to avoid Jack of All Trades' classes but in a different manner. The open-closed says that instead of modifying your base classes to add more features you should create a new class with those features, and this class will be inheriting the base class.

Imagine having a class that based on the input could go through one workflow or another workflow completely different, you will probably manage that with conditional statements.

Now, imagine when you need to do maintenance in one of those workflows. What is the risk of breaking, by mistake, the other ones?

The Open-Closed Principle says that you must have a base class that should not be modified to add new functionalities. Instead, you should have a new class inheriting the base class with those new functionalities. In this case, when you need to do maintenance in a specific workflow you are certain that will have no side effects in the others workflows.

L - LSP - Liskov Substitution Principle
"Subtypes must be substitutable for their base types." (Barbara Liskov)

The main point from the Liskov Substitution Principle is to create a base class in order to be inherited from its subtypes.

The Liskov Substitution Principle states that if we change our class for its base class we should not have different behaviour. Instead, if we change our subclasses by our base class without breaking the application because our base classes must behave in the same way as our subclasses. In this case, if we need to replace our subclasses for another subclass we would not need to refactor anything because it would be a simple substitution and everything would still be working the same.

I - ISP - Interface Segregation Principle
"No client should be forced to depend on methods it does not use." (Robert C. Martin)

This principle is not the best friend of laziness, it is the worst friend of those projects when you place all your needed contracts in a single place, with this we are not going to be able to reuse those interfaces or, even worse, we will not be using every method in the contract. Instead of creating an interface with many contracts on it, you should create as many interfaces as possible by grouping common behaviours.

The Interface Segregation Principle says that we should segregate interfaces in order to do not depend on interfaces with a contract that will not be implemented. We should have smaller interfaces and depend on more than one interface if needed.

D - DIP - Dependency Inversion Principle
"High-level modules should not depend upon low-level modules. Both should depend upon abstractions" (Robert C. Martin)

Here we have the best friend of loosely coupled applications and unit testing. If you have ever needed to replace one dependency and to do this you had to refactor every method that this dependency was being called then you should use the Dependency Inversion Principle.

The Dependency Inversion Principle says that our classes should not depend on low-level modules because this created a strong coupled application, making it very difficult to unit test and substitute dependencies. Instead, our classes should receive in their constructor the abstraction of their low-level dependencies, with this we may change our classes that implement the contract without breaking or needing to refactor the whole application. Also, we could easily mock our dependencies in order to create unit tests.

SOLID principles Practical Usage One-by-One
SRP - The Problem - Class that sum two numbers and log it
Here we have a problem that we can use the Single Responsability Principle to fix it. A class with two functionalities, the first is to sum two numbers and the second is to log this operation
class Problem
{
    public int Sum(int numberOne, int numberTwo)
    {
        int result = numberOne + numberTwo;
        LogCalculations("Sum Operation. Number one: " + numberOne + ", Number two: " + numberTwo + ". Result: " + result);
        return result;
    }

    private void LogCalculations(string message)
    {
        string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\log.txt";
        using (StreamWriter sw = File.CreateText(path))
        {
            sw.WriteLine(message);
        }
    }
}


Applying the Single Responsibility Principle we would have two different classes:
class Solution
{
    public int Sum(int numberOne, int numberTwo)
    {
        int result = numberOne + numberTwo;
        Logging.Log("Sum Operation. Number one: " + numberOne + ", Number two: " + numberTwo + ". Result: " + result);
        return result;
    }
}
public static class Logging
{
    public static void Log(string message)
    {
        string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\log.txt";
        using (StreamWriter sw = File.CreateText(path))
        {
            sw.WriteLine(message);
        }
    }
}


OCP - The Problem 1 - Class to do math calculations
In this class, we receive as input one enumeration with the operation type and return the result from this operation. If we need to add or modify one operation, we would need to modify the class.
public class MathCalculate
{
    public double Calculate(double numberA, double numberB, CalculationType calculationType)
    {
        double result = 0;
        switch (calculationType)
        {
            case CalculationType.addition:
                result = numberA + numberB;
                break;
            case CalculationType.multiplication:
                result = numberA * numberB;
                break;
            case CalculationType.subtraction:
                result = numberA - numberB;
                break;
            default:
                break;
        }
        return result;
    }
}

public enum CalculationType
{
    addition,
    multiplication,
    subtraction
}


Applying the Open-Closed Principle we would have a base class "Calculation" and each operation would be a new class, inheriting from this base class. If we need to add or modify a new operation, then we would need to work only in the class of this operation
public abstract class BaseCalculation
{
    public abstract double Calculate(double numberA, double numberB);
}

public class AdditionCalculation : BaseCalculation
{
    public override double Calculate(double numberA, double numberB)
    {
        return numberA + numberB;
    }
}
public class MultiplicationCalculation : BaseCalculation
{
    public override double Calculate(double numberA, double numberB)
    {
        return numberA * numberB;
    }
}

public class SubtractionCalculation : BaseCalculation
{
    public override double Calculate(double numberA, double numberB)
    {
        return numberA - numberB;
    }
}

public class DivisionCalculation : BaseCalculation
{
    public override double Calculate(double numberA, double numberB)
    {
        return numberA / numberB;
    }
}


OCP - The Problem 2 - Email sender with validations
If we need to add or modify the validations, we would need to modify the email sender class, risking to break the email sender functionality.
public class MailSender
{
    public void SendMail(string subject, string body, string recipient)
    {
        SmtpClient smtpClient = new SmtpClient("smtp.gmail.com")
        {
            Port = 587,
            Credentials = new NetworkCredential("email", "password"),
            EnableSsl = true,
        };

        //validate recipient's domain
        if (!recipient.ToString().EndsWith("@thiago.com"))
        {
            Console.WriteLine("Mail destinatary not in the domain");
            return;
        }

        //validate body
        if (string.IsNullOrEmpty(body))
        {
            Console.WriteLine("Mail body is empty.");
            return;
        }

        smtpClient.SendAsync("[email protected]", recipient, subject, body, null);
    }
}


Applying the open-closed principle to solve this problem we would have this email sender class receiving a list of validations. When we need to add or modify one of those validations would not be necessary to update the mail sender class:
public class MailClass
{
    public string Subject { get; set; }
    public string Body { get; set; }
    public string Recipient { get; set; }
}

public interface IValidation<T>
{
    bool Validate(T mail);
}


public class DomainValidation : IValidation<MailClass>
{
    public bool Validate(MailClass mail)
    {
        if (mail.Recipient.ToString().EndsWith("@thiago.com"))
            return false;

        return true;
    }
}
public class BodyValidation : IValidation<MailClass>
{
    public bool Validate(MailClass mail)
    {
        if (string.IsNullOrEmpty(mail.Body))
            return false;

        return true;
    }
}

public class Main
{
    public void SendMail(MailClass mailClass, List<IValidation<MailClass>> validations)
    {
        List<bool> validationsResult = new List<bool>();
        validations.ForEach(x => validationsResult.Add(x.Validate(mailClass)));

        if (!validationsResult.Any(x => !x))
        {
            SmtpClient smtpClient = new SmtpClient("smtp.gmail.com")
            {
                Port = 587,
                Credentials = new NetworkCredential("email", "password"),
                EnableSsl = true,
            };

            smtpClient.SendAsync("[email protected]", mailClass.Recipient, mailClass.Subject, mailClass.Body, null);
        };
    }
}


OCP - Extension Methods
We can also make usage of extension methods in order to extend objects without modifying their base behaviour. The following code extends the String object, adding this new method on it:
public static class SolutionThree
{
    public static string ThiagoString(this string normalString)
    {
        return "Thiago's String is: " + normalString;
    }
}

LSP - The Problem - Having a subtraction class inheriting from an addition class
In this example, we have an addition class working fine and then we extended it, wrongly, creating the subtraction class. If we replace the subtraction class with the base class (addition) our output will differ.
public class AdditionCalculation
{
    public AdditionCalculation(int numberA, int numberB)
    {
        this.NumberB = numberB;
        this.NumberA = numberA;
    }
    public int NumberA { get; set; }
    public int NumberB { get; set; }
    public virtual int Calculate()
    {
        return this.NumberA + NumberB;
    }
}
public class SubtractionCalculation : AdditionCalculation
{
    public SubtractionCalculation(int numberA, int numberB) : base(numberA, numberB)
    {
    }

    public new int Calculate()
    {
        return NumberA - NumberB;
    }
}

Applying the Liskov Substitution Principle to solve this problem we have a base class then we have the addition and subtraction classes inheriting from this base class. If we change our class with the base class our output is not going to be affected.
public abstract class MathCalculate
{
    public MathCalculate(int numberA, int numberB)
    {
        this.NumberB = numberB;
        this.NumberA = numberA;
    }
    public int NumberA { get; set; }
    public int NumberB { get; set; }

    public abstract int Calculate();
}
public class Addition : MathCalculate
{
    public Addition(int numberA, int numberB) : base(numberA, numberB)
    {
    }

    public override int Calculate()
    {
        return this.NumberA + NumberB;
    }
}
public class Subtraction : MathCalculate
{
    public Subtraction(int numberA, int numberB) : base(numberA, numberB)
    {
    }

    public override int Calculate()
    {
        return NumberA - NumberB;
    }
}

ISP - The Problem - A single interface for many type of classes
In this example, we have a single interface called IAnimal that is used for both Human and Whale classes. The problem is that not the whole contract fit both items because, as we all know, whales do not walk.
public interface IAnimal
{
    void Walk();
    void Breath();
    void Eat();
    void Argument();
}

public class Human : IAnimal
{
    public void Argument()
    {
        // Argumentation
    }

    public void Breath()
    {
        // Breathing
    }

    public void Eat()
    {
        // Eating
    }

    public void Walk()
    {
        // Walk
    }
}
public class Whale : IAnimal
{
    public void Argument()
    {
        // Argumentation
    }

    public void Breath()
    {
        // Breathing
    }

    public void Eat()
    {
        // Eating
    }

    public void Walk()
    {
        throw new NotImplementedException();
    }
}

Applying the Interface Segregation Principle we break the single interface in smaller interfaces with common methods and then we only use the interfaces that we need in our classes.
public interface IFeed {
    void Eat();
}

public interface IArgument
{
    void Argument();
}

public interface IGroundMoviment
{
    void Walk();
}
public interface IAirMoviment
{
    void Fly();
}
public interface IWaterMoviment
{
    void Swimm();
}

public class Human : IGroundMoviment, IArgument, IFeed
{
    public void Argument()
    {
        // Argument
    }

    public void Eat()
    {
        // Eat
    }

    public void Walk()
    {
        // Walk
    }
}
public class Whale : IWaterMoviment, IFeed
{
    public void Eat()
    {
        // Eat
    }

    public void Swimm()
    {
        // Swimm
    }
}


DIP - The Problem - Strong Coupled Application
In this example, we need to instantiate our dependency inside our method, creating a strong coupled application. If we want to replace this dependency we would need a lot of work, needing to change those dependencies at every method that is being used. Also, it is much more difficult to unit test this layer because it is much harder to mock this dependency.
public class BusinessLayer
{
    public void AddItem(int itemId)
    {
        RepositoryLayer repositoryLayer = new RepositoryLayer();
        if (!string.IsNullOrEmpty(repositoryLayer.GetItem(itemId)))
            repositoryLayer.Update();
        else
            repositoryLayer.Create();
    }
}
public class RepositoryLayer
{
    public void Create()
    {
        //save data into the Database
    }
    public void Delete()
    {
        //delete data from the Database
    }
    public void Update()
    {
        //update data in the Database
    }
    public string GetItem(int itemId)
    {
        //get item from the Database
        return "item";
    }
}

Applying the Dependency Inversion Principle, we receive the interface of our dependency in the class constructor. If we need to change the class that implements this dependency contract it would not be necessary to refactor any method where we use this dependency.
public class BusinessLayer
{
    private readonly IRepositoryLayer repositoryLayer;

    public BusinessLayer(IRepositoryLayer repositoryLayer)
    {
        this.repositoryLayer = repositoryLayer;
    }
    public void AddItem(int itemId)
    {
        if (!string.IsNullOrEmpty(repositoryLayer.GetItem(itemId)))
            repositoryLayer.Update();
        else
            repositoryLayer.Create();
    }
}
public interface IRepositoryLayer
{
    void Create();

    void Delete();

    void Update();

    string GetItem(int itemId);
}

Congratulations! You have successfully applied the SOLID principles using .Net Core.

HostForLIFE ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core Hosting - HostForLIFE :: Build Up Swagger In ASP.Net Core 2.2 Web API

clock March 27, 2023 09:16 by author Peter

What is Swagger?
Swagger is the largest framework for designing APIs using a common language and enabling the development across the whole API lifecycle, including documentation, design, testing, and deployment.

The framework provides a set of tools that help programmers generate client or server code and install self-generated documentation for web services.

What is Swashbuckle?
Swashbuckle is a handy library to easily bring Swagger support to your ASP.NET Core (or ASP.NET) application. It is especially handy when developing an HTTP based API. It creates a form of interactive documentation based on the OpenAPI Specification.

Add Swagger to ASP.NET Core 2.2 Web API
Create an ASP .NET Core 2.2 Web API project using Visual Studio 2019.

After creating the project, go to Nuget Package Manager and add Swashbuckle.AspNetCore package.

Open Startup.cs file to add swagger service to middleware. Like:

And enable the Swagger UI in Configure() method.

 

To open Swagger UI as your home page.In Debug tab from the properties of the solution, you will find a check box “Launch browser”. Change the value in a text box with “swagger”.

Now, when you run the application you should see Swagger UI for ValuesController.

Manage versions of APIs
You can manage different versions of your APIs, for example, you have v2 APIs as well and you need to enable that in Swagger then you just need to add a couple of steps.
First, add new SwaggerDoc in ConfigureService method and then add new endpoint in Configure method in Startup.cs


To Include XML Comments in Swagger, we recommend reading this article Include XML Comments in Swagger under ASP.NET Core 2.2.
The source code for this tutorial is available on GitHub.

HostForLIFE ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core Hosting - HostForLIFE :: What is Microservices In .NET Core? Why do we need Microservices?

clock March 24, 2023 08:22 by author Peter

Microservices is an architectural style for building software applications as a collection of small, independent, loosely-coupled services that communicate with each other through well-defined APIs. Each microservice is responsible for performing a specific task within the application and can be developed, deployed, and scaled independently of other services.

In a microservices architecture, the application is divided into a set of small, independent services that can be developed and deployed separately. Each microservice can be built using a different technology stack and maintained by a separate development team. This approach allows teams to work independently, iterate quickly, and deliver software more frequently.

Microservices also promote containerization technologies, such as Docker, OpenShift, and Kubernetes, to manage the deployment and scaling of services. This allows services to be scaled up or down based on demand, making the application more resilient and responsive. Microservices architecture is well-suited for complex, large-scale applications that require high scalability, flexibility, and agility. It provides a way to build and maintain complex applications with a high degree of autonomy and fault tolerance.

For example, think of a system for managing a warehouse. If you broke down its capabilities, you might come up with the following list,

Inventory tracking
The system can keep track of inventory levels and locations within the warehouse, using barcode or RFID technology to identify and track products.

Order management
The system can manage orders, including picking and packing, and help ensure that the correct products are shipped to customers.

Receiving
The system can manage the receiving process, including inspecting and verifying incoming shipments and updating inventory levels.

Shipping
The system can manage shipping processes, including generating shipping labels, tracking shipments, and updating inventory levels.

Reporting
The system can provide reports on inventory levels, order status, and other key performance indicators to help managers make informed decisions.

Integration
The system can integrate with other systems, such as e-commerce platforms, to streamline the flow of information between different parts of the business.

Automation
The system can automate many tasks, such as inventory counts, order processing, and shipping label generation, to reduce errors and improve efficiency.

Further, if we broke the Receiving process's capabilities, we might come up with a process of receiving and processing incoming shipments or deliveries in a warehouse or distribution center. This microservice typically handles the following tasks,

Receiving and logging incoming shipments

The receiving process microservice is responsible for receiving and logging incoming shipments or deliveries, including verifying their contents and quantities.

Managing inventory

The microservice is responsible for updating the inventory records and tracking the location of received items. Coordinating with other systems: The receiving process microservice communicates with other systems, such as the order management system, to ensure that received items are allocated correctly.

Quality control

The microservice may perform quality control checks on incoming shipments to ensure the products meet the required standards.

Reporting
The microservice generates reports on received shipments, inventory levels, and other relevant metrics.

Each little capability in the system is implemented as an individual microservice. Every microservice in a system

  • It runs in its own separate process
  • It can be deployed on its own, independently of the other microservices
  • It has its own dedicated data store
  • Collaborates with other microservices to complete its own action


Microservice characteristics
A microservice is responsible for a single capability

Each microservice is designed to perform a specific function or capability in a self-contained and autonomous manner.

A microservice is individually deployable
Microservices are designed to be deployed independently, allowing for more frequent updates and releases.

A microservice consists of one or more processes
Microservices are typically built using a single process but may include additional processes to handle specific tasks or functions.

A microservice owns its own data store

Each microservice has its own database or data store, allowing it to manage its data independently of other services.

A small team can maintain a handful of microservices
Microservices are designed to be relatively small and focused, making them easier to maintain and update.

A small team can typically manage a handful of microservices.

A microservice is replaceable
Microservices are designed to be loosely coupled, meaning that they can be replaced or updated without affecting the overall system.

This allows for greater flexibility and agility in responding to changing business needs.

Summary

Microservices represent a powerful architectural approach to software development that offers many benefits. By breaking down applications into smaller, independent services, microservices allow for greater flexibility, scalability, fault tolerance, and specialized team structures. However, successfully implementing microservices requires careful planning, coordination, and testing to ensure that each service works seamlessly. It's also important to regularly monitor and update your microservices to maintain their performance and reliability.

Overall, microservices offer a promising approach to software development that can help organizations build more efficient, scalable, and resilient applications.

Note
Suppose you're considering implementing microservices in your organization. In that case, it's important to carefully evaluate your needs and resources and work with experienced developers and architects to design a system that meets your unique requirements.

Thank you for reading, and I hope this blog post has helped provide you with a better understanding of microservices.

HostForLIFE ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.

 



European ASP.NET Core Hosting - HostForLIFE :: How To Write Resilient Code Using Polly In .NET 6?

clock March 20, 2023 08:46 by author Peter

In today's fast-paced world of software development, it's essential to have reliable and resilient applications. With the rise of cloud computing and microservices, network failures and system errors are inevitable. In such scenarios, retrying the failed operations is a common solution to keep the application up and running. Polly is a powerful .NET library that provides a flexible and easy-to-use solution to handle such scenarios.

This article will discuss how to use Polly in .NET 6 to handle network failures, retries, and circuit breakers.

What is Polly?
Polly is an open-source library for .NET that provides a simple and elegant way to handle transient errors, retries, and circuit breakers. It allows developers to define policies to handle different types of exceptions and failures. Polly is flexible, extensible, and easy to use, making it an excellent choice for building resilient applications.

Getting started with Polly
To use Polly in your .NET 6 application, you must add the Polly NuGet package to your project. You can do this by using the NuGet Package Manager or adding the package reference to the .csproj file.

Once you have added the Polly package to your project, you can use it in your code. Here's a simple example of how to use Polly to handle a network failure and retry the operation.
using Polly;
using System;
using System.Net.Http;
public static async Task < string > GetResponseData(string url) {
    var policy = Policy.Handle < HttpRequestException > ().WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
    var client = new HttpClient();
    var response = await policy.ExecuteAsync(async () => await client.GetAsync(url));
    return await response.Content.ReadAsStringAsync();
}


In the above code, we define a policy that handles HttpRequestException and retries the operation up to three times with an exponential backoff strategy. The WaitAndRetryAsync method takes two parameters: the number of retries and a function that calculates the delay between retries.

We then create an instance of the HttpClient and use the ExecuteAsync method of the policy to execute the GET request. Polly will automatically retry the operation according to the defined policy if the request fails due to a network failure.
Handling Circuit Breakers

Circuit breakers are another important aspect of building resilient applications. They prevent an application from repeatedly making requests to a failing service, which can overload the system and cause it to crash.

Polly provides an easy-to-use circuit breaker implementation that allows you to define a threshold for failed operations. Once the threshold is reached, the circuit breaker will trip and stop executing requests for a specified amount of time.

Here's an example of how to use Polly's circuit breaker,
var policy = Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(30)
    );

try
{
    await policy.ExecuteAsync(async () => await client.GetAsync(url));
}
catch (Exception ex)
{
    // Handle the exception
}


In the above code, we define a policy that handles HttpRequestException and trips the circuit breaker after two consecutive failures. The circuit breaker remains open for 30 seconds, after which it resets and allows requests to be executed again.

In conclusion, Polly is a powerful library that provides a flexible and easy-to-use solution for handling network failures, retries, and circuit breakers. Polly's simple and elegant API makes it easy to build resilient applications that can handle transient errors and keep running under adverse conditions. By using Polly in your .NET 6 application, you can make your application more reliable, and scalable.

HostForLIFE ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core Hosting - HostForLIFE :: Implementation Of NLog With .NET 6 Web API

clock March 13, 2023 08:22 by author Peter

As a Modern web application, we do a lot of operations in it. To handle more requests, we need a robust logging mechanism for monitoring.

Henceforth I've shared One of the best logging mechanisms implemented for the .NET 6 web application.

By default, Microsoft has a logging mechanism, but for our convenience, we go with third-party log providers, which is helpful to make how we need our logs to be said, for example, file, console, etc...

I'm using NLog as a third-party log provider (you can learn more about NLog here NLog under config options).

Hereby I've given some short notes about logs done in NLog. For NLog configuration, you need the "NLog.config" file. Inside that, you have an XML file describing the logging configuration.

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!--
  See https://github.com/nlog/nlog/wiki/Configuration-file
  for information on customizing logging rules and outputs.
   -->
  <targets async="true">
     <target name="console" xsi:type="Console" layout="${longdate} ${level:upperCase=true} ${logger}:${callsite-linenumber} - ${scopeproperty:item=requestId} - ${message}" />
  </targets>
  <rules>
    <!-- Log all events to the json-file target -->
    <logger name="*" writeTo="console" minlevel="Info" />
  </rules>
</nlog>


Here an XML syntax with encoding UTF-8 will be created and followed by NLog tag, which was a basic syntax. Then targets tag is used to specify how we need the logs, whether in a file format, mail or console, or other formats. Inside <target> tag, you need to specify xsi:type="Console" (hereby, I need console logs). Then what layout do you need to specify under the layout property, which describes the details you need inside the logs?

Then follows the rules section. Under this, you need to specify the logger, which has the restriction part, which gives the log level you need, the logger's name, and where to write it.

For this implementation, you must add the relevant dependency package from nuget.org to your ".csproj" file.
<PackageReference Include="NLog.Web.AspNetCore" Version="5.2.1" />

Inside the layout, we have some config renderers,
${longdate} - used for date time printing when the logs get printed in the sortable format `yyyy-MM-dd HH:mm:ss.ffff`.
${level:upperCase=true} - used to print by which log level we're logging. I've mentioned the default log levels with their precedence (if set minimum level, then you mention inside the rules under logger tag set property min level = "info" then greater than info-2 (Warning-3, Error-4, Critical-5 )and info-2 logs are getting caught, Debug and Trace won't) below
    public enum LogLevel {
        Trace = 0,
            Debug = 1,
            Information = 2,
            Warning = 3,
            Error = 4,
            Critical = 5,
            None = 6
    }

    ${logger} - Name of the logger, the class name in most common cases.

    ${callsite-linenumber} - which gives the line number where the log gets jumped out (Error case like a try-catch exception) and prints the log statement where it is held up.

    ${scopeproperty:item=requestId} - which is of dynamic value assignment for user convenience like context headers or request-based information. Then you can assign the values inside the code (Earlier MDC, MDLC, NDC, and NDLCs were used but not in the current version. They were put up in a single one).
    ${message} - For log messages.

I'm declaring the logger like this in my class for the basic declaration of the logger.
private static Logger logger = LogManager.GetLogger("WeatherForecastController");

By using this logger, you can access the different methods. Here I'm mentioning the info log.
logger.Info("Inside GetWeatherForecast Controller");

Earlier, I said that scope property is a dynamic one. You can set it on your own, which can be set as,
ScopeContext.PushProperty("requestId",1001);

HostForLIFE ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core Hosting - HostForLIFE :: Step-by-Step Guide To Develop Tic-Tac-Toe AI With Blazor

clock March 9, 2023 09:30 by author Peter

Step-by-Step Guide To Develop Tic-Tac-Toe AI With Blazor

Tic Tac Toe is a popular game that has been enjoyed for generations, on the other hand, our generation enjoys AI. It's about time we clash them together. In this article, we will be designing Tic Tac Toe in Blazor and developing AI-driven game logic with the Min Max algorithm, an artificial intelligence algorithm used for game decision-making.

What is the Min Max Algorithm?
The Min Max algorithm is used to design digital board games like chess and checkers to help determine the best possible move for a player. The algorithm works by evaluating all possible moves a player can make and predicting the best outcome of each move.

To begin with, let me lay down the steps:

    Create a new Blazor application.
    Create a new component called "TicTacToe".
    Iterate div tags under 2 for loops to create a 3x3 matrix
    Create a char array of 3x3 to support matrix data, and create a list of winning combinations
    Add a method that will be called when the user clicks on a cell in the grid
    Design a MinMax algorithm to suggest the next possible best move
    Use the MinMax algorithm with the Blazor app to predict the next best move for AI
        Check if the cell is already occupied. If it is, return
        Call the MinMax algorithm to get the next best move
        Update the cell with the current player's mark (X or O).
        Check if the game has been won or if it is a tie.
        If the game is finished, show animation with JavaScript and reset the grid.
        Repeat the above steps until the game is won or a tie is reached.

Let's take a closer look at each of these steps.

Step 1. Create a new Blazor application
To create a new Blazor application, open Visual Studio and select "Create a new project". In the "Create a new project" dialog, select "Blazor WebAssembly App" or "Blazor Server App" and click "Next".

Give your project a name and click "Create".


Step 2. Add a new razor component called "TicTacToe"
To add a new component, right-click on the project, select "Add" then select "New item", which will bring the dialog below, then select "Razor component", give it a name, and click on the "Add" button.


Step 3. Iterate div tags under 2 for loops to create a 3x3 matrix
In the "TicTacToe" component, add the following HTML to create a 3x3 grid:

@page "/"
@inject IJSRuntime JS

<div>
    <div class="board">
        @for(int i=0; i < 3; i++)
        {
            int row = i;
            for (int j = 0; j < 3; j++)
            {
                int col = j;
                <div class="square" @onclick="()=> SquareCliked(row, col)">
                    <h5 class="char">@Board[row, col]</h5>
                </div>
            }
        }
    </div>
</div>


Listing 1: TicTacToe.razor (HTML)
Step 4. Create a char array of 3x3 to support matrix data and create a list of winning combinations
Create an empty char array, "Board", and create a char to represent a "Player", There are 8 possible winning combos, create a list of "WinningCombos"
@code {

    char[,] Board = { { ' ', ' ', ' ' }, { ' ', ' ', ' ' }, { ' ', ' ', ' ' } };
    char Player = 'o';
    List<List<int[]>> WinningCombos = new()
    {
        //First row
        new List<int[]>() {new int[] { 0,0 }, new int[] { 0, 1 }, new int[] { 0, 2} },
        //Second row
        new List<int[]>() {new int[] { 1,0 }, new int[] { 1, 1 }, new int[] { 1, 2} },
        //Third row
        new List<int[]>() {new int[] { 2,0 }, new int[] { 2, 1 }, new int[] { 2, 2} },

        //First column
        new List<int[]>() {new int[] { 0,0 }, new int[] { 1, 0 }, new int[] { 2, 0} },
        //Second column
        new List<int[]>() {new int[] { 0,1 }, new int[] { 1, 1 }, new int[] { 2, 1} },
        //Third column
        new List<int[]>() {new int[] { 0,2 }, new int[] { 1, 2 }, new int[] { 2, 2} },

        //Backward diagonal
        new List<int[]>() {new int[] { 0,0 }, new int[] { 1, 1 }, new int[] { 2, 2} },
        //Forward diagonal
        new List<int[]>() {new int[] { 0,2 }, new int[] { 1, 1 }, new int[] { 2, 0} },
    };
}


Listing 2: TicTacToe.razor (C#)
Step 5. Add a method that will be called when the user clicks on a cell in the grid
private async Task SquareCliked(int row, int col)
{

}

Listing 3: TicTacToe.razor (C#)
Step 6. Design a MinMax algorithm to suggest the next possible best move
Let's write the algorithm in a separate file. Right-click on project > add a folder, name "AI," then add a C# file inside the folder, name it "MinMaxAlgorithm.cs"
namespace BlazingTicTacToe.AI
{
    public class MinMaxAlgorithm
    {
        public class Turn
        {
            public int row, col;
        };

        private static readonly char Player = 'x';
        private static readonly char Opponent = 'o';
        private static readonly char EmptyCell = ' ';

        //Returns true if there are moves left
        static bool AreMoveLeft(char[,] board)
        {
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    if (board[i, j] == EmptyCell)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        static int GetCurrentScore(char[,] board)
        {
            // Validate for Rows.
            for (int i = 0; i < 3; i++)
            {
                if (board[i, 0] == board[i, 1] && board[i, 1] == board[i, 2])
                {
                    if (board[i, 0] == Player)
                    {
                        return +10;
                    }
                    else if (board[i, 0] == Opponent)
                    {
                        return -10;
                    }
                }
            }

            // Validate for Columns.
            for (int j = 0; j < 3; j++)
            {
                if (board[0, j] == board[1, j] && board[1, j] == board[2, j])
                {
                    if (board[0, j] == Player)
                    {
                        return +10;
                    }

                    else if (board[0, j] == Opponent)
                    {
                        return -10;
                    }
                }
            }

            // Validate for Backward diagonal.
            if (board[0, 0] == board[1, 1] && board[1, 1] == board[2, 2])
            {
                if (board[0, 0] == Player)
                {
                    return +10;
                }
                else if (board[0, 0] == Opponent)
                {
                    return -10;
                }
            }
            // Validate for Forward diagonal.
            if (board[0, 2] == board[1, 1] && board[1, 1] == board[2, 0])
            {
                if (board[0, 2] == Player)
                {
                    return +10;
                }
                else if (board[0, 2] == Opponent)
                {
                    return -10;
                }
            }

            return 0;
        }


        static int ComputeMinMax(char[,] board, int depth, bool isMax)
        {
            int score = GetCurrentScore(board);

            // If Max has won the game
            if (score == 10) return score;

            // If Mini has won the game
            if (score == -10) return score;

            // If it is a tie
            if (AreMoveLeft(board) == false) return 0;

            // Max move
            if (isMax)
            {
                int bestValue = -1000;

                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        if (board[i, j] == EmptyCell)
                        {
                            // Make the move
                            board[i, j] = Player;

                            // Call ComputeMinMax recursively to get max
                            bestValue = Math.Max(bestValue, ComputeMinMax(board, depth + 1, !isMax));

                            // Undo the move
                            board[i, j] = EmptyCell;
                        }
                    }
                }
                return best;
            }
            else
            {
                int bestValue = 1000;

                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        if (board[i, j] == EmptyCell)
                        {
                            // Make the move
                            board[i, j] = Opponent;

                            // Call ComputeMinMax recursively to get min
                            bestValue = Math.Min(bestValue, ComputeMinMax(board, depth + 1, !isMax));

                            // Undo the move
                            board[i, j] = EmptyCell;
                        }
                    }
                }
                return bestValue ;
            }
        }

        // AI will select best possible move
        public static Turn GetNextBestMove(char[,] board)
        {
            int bestValue = -1000;
            Turn bestTurn = new()
            {
                row = -1,
                col = -1
            };

            // GetCurrentScore ComputeMinMax function And return the cell with best value.
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    if (board[i, j] == EmptyCell)
                    {
                        board[i, j] = Player;
                        int currentTurnValue = ComputeMinMax(board, 0, false);

                        // Undo the move
                        board[i, j] = EmptyCell;

                        if (currentTurnValue > bestValue)
                        {
                            bestTurn.row = i;
                            bestTurn.col = j;
                            bestValue = currentTurnValue;
                        }
                    }
                }
            }
            return bestTurn;
        }
    }
}


Listing 4: MinMaxAlgorithm.cs
Step 7. Use the MinMax algorithm to predict the next best move for AI and Update the cell with the current player's mark (X or O)
private async Task SquareCliked(int row, int col)
{
    if (Board[row, col] != ' ') return;
    Board[row, col] = Player;

    Turn currentTurn = MinMaxAlgorithm.GetNextBestMove(Board);
    if(!(currentTurn.row == -1 && currentTurn.col == -1))
    Board[currentTurn.row, currentTurn.col] = 'x';

    foreach (var combo in WinningCombos)
    {
       int[] first = combo[0];
       int[] second = combo[1];
       int[] third = combo[2];
       if (Board[first[0], first[1]] == ' ' || Board[second[0], second[1]] == ' ' || Board[third[0], third[1]] == ' ') continue;
       if (Board[first[0], first[1]] == Board[second[0], second[1]] && Board[second[0], second[1]] == Board[third[0], third[1]] && Board[first[0], first[1]] == Board[third[0], third[1]])
        {
               string winner = Player == 'o' ? "AI" : "Player ONE";
               await JS.InvokeVoidAsync("ShowSwal", winner);
               await Task.Delay(1000);
               ResetGame();
         }
      }

      if (IsGameReset())
      {
          await JS.InvokeVoidAsync("ShowTie");
          ResetGame();
      }
}


Listing 5: TicTacToe.razor, SquareClick method (C#)

Add a few helper methods to reset the game.
private bool IsGameReset()
{
    bool isReset = true;
    for (int i = 0; i < 3; i++)
    {
       for (int j = 0; j < 3; j++)
       {
           if(Board[i, j] == ' ')
           {
              isReset = false;
           }
       }
    }
    return isReset;
}

private void ResetGame()
{
   for (int i = 0; i < 3; i++)
   {
      for (int j = 0; j < 3; j++)
      {
          Board[i, j] = ' ';
      }
    }
}


Listing 5: TicTacToe.razor, helper methods (C#)
You must wonder what those statements are in listing 5 > line numbers 19 and 27. Well, that's how we call javascript methods using JSRuntime.

There are 2 scenarios when we are calling JS,

    When either Player 1 or 2 wins.
    If the game is tied.

First and foremost, go to wwwroot, and create a new folder named "js" inside the folder, add a new javascript file, name it common.js

There are 2 methods,
ShowSwal means to show a sweet alert. In code snippet 2 at line 34, we mention this method name as a parameter, so JsRuntime looks for the same method we specify as a parameter.
ShowTie, representing the tie, in code snippet 2 at line number 41, we are specifying this method name as a parameter.
window.ShowSwal = (player) => {
    Swal.fire({
        title: player + ' won!!',
        width: 350,
        padding: '3em',
        color: '#716add',
        backdrop: `
                        rgba(0,0,123,0.4)
                        url("/images/nyan-cat-nyan.gif")
                        left top
                        no-repeat
                      `
    })
}
window.ShowTie = () => {
    Swal.fire({
        title: 'Go home, nobody won!',
        width: 350,
        padding: '3em',
        color: '#716add',
        backdrop: `
                        rgba(0,0,123,0.4)
                        url("/images/crying-tear.gif")
                        left top
                        no-repeat
                      `
    })
}


Listing 6: Common.js
Now let's integrate JavaScript with blazor app.
Open Index.html under wwwroot folder. And inside a head tag, add these 3 script tags.

<script src="js/common.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="sweetalert2.min.js"></script>

Listing 7: Index.html
The CSS
we are almost done, but it's not done unless we have some CSS, right? If you remember, for code snippet 1, we have added a bunch of classes to our divs. Let's code those classes in a separate CSS file.

Here is the trick to create a razor specific CSS file. Click on the folder named "pages" and say "Add new item" then select Style Sheet. Here you have to give the same name as your razor file. For our example, we will name TicTacToe.razor.css, refer image below.


Now you will see how newly added CSS is automatically assigned below the razor component.

Here is the CSS with basic properties with flex and hover.
* {
    padding: 0;
    margin: 0;
}

h1 {
    text-align: center;
    margin-bottom: 10px;
}

p {
    margin-top: 10px;
    text-align: center;
    font-family: cursive;
}

.board {
    width: 22rem;
    height: 22rem;
    margin: auto;
    border: 1px solid white;
    display: flex;
    flex-wrap: wrap;
}

.square {
    width: 7rem;
    height: 7rem;
    border: 1px solid white;
    margin-right: 4px;
    border-radius: 30px;
    background: #78bec5;
    opacity: 80;
}

    .square:hover {
        background: #ecaf4f;
        cursor: pointer;
    }

.char {
    font-size: 3.5rem;
    text-align: center;
    font-weight: 800;
    margin-top: 15%;
    color: #dc685a;
    font-family: cursive;
}


Listing 8: TicTacToe.razor.css
Conclusion
I believe, Implementing the Tic Tac Toe game using the Min Max algorithm in Blazor was a great learning experience. This algorithm is widely used in game development and can help you create more intelligent and challenging games.

Blazor provides an excellent platform to implement this game as it uses C# language to write the code, making it easier for most developers. It also allows you to create interactive UI and responsive web applications using HTML, CSS, and JavaScript.

HostForLIFE.eu ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core Hosting - HostForLIFE :: Structured Logging Using Serilog In ASP.NET Core 7.0

clock March 7, 2023 08:02 by author Peter

In this article, we will learn how to do structured logging using Serilog in asp.net core 7.0.

Logging is an essential part of an application. It helps us to track the application and even helps us to find the root cause of an error after the deployment of an application into production.

When we work with the asp.net core application, then we have an ILogger interface that helps us to do logging. But what if we want to have more control of the application? What if we want to have logging in a more structured way and with more detail? Then the Logging Frameworks or the Libraries come into the picture.

 There are many ways through which we can do the logging, and every tool has cons and pros. Here we will discuss in detail Serilog, which helps us to log in a structured way. Serilog libraries are among the most popular for .NET Core Applications.

What is Serilog?
Serilog is the third-party logging library that overrides the default ILogger library and implements its own methods and logic in a structured way. It helps developers log events or messages into various applications like consoles, web, databases, etc.

Serilog supports structured logging, which enables more logging details and information about the event logged in the application. This logging helps us to find the root cause while debugging the code, or if we get the error in production, a fast fix to the end user.

To implement Serilog, we first need to create an asp.net core API application. So for that, let's open visual studio 2022 and click on create a new project and select an Asp.net core web application template.

Now give the project name 'SerilogApp' and click Next.


Now select the framework and click next

Once you click on next, it will create an application.
Now right-click on the application and go to the NuGet package manager.

In the NuGet package manager window, select the "Serilog.AspNetCore" and install it like below

If you don't want to install "Serilog.AspNetCore" from the NuGet package manager, then you can run the below command as well in the console package manager,
Install-Package Serilog.AspNetCore

Once the package is installed successfully, we must configure it in the program.cs class

we can call the UseSerilog function in the HostBuilder instance to configure Serilog using a lambda expression.

The simplest way to configure Serilog in the application is by calling ReadFrom.Configuration().

We can also use the UseSerilogRequestLogging() method to introduce the automatic HTTP request logging in the asp.net core API application.
using Serilog;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, configuration) => configuration.ReadFrom.Configuration(context.Configuration));
// 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.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();


Structured Logging using Serilog in ASP.NET Core 7.0

Configuring Serilog With the appsettings.json
We need to add a new Serilog section block in the appsettings.json file.

Here we can configure below things:
    Logging sinks to use with the Serilog
    Override the default and the minimum log levels
    Configure the file-logging arguments

Here we will add the Console and the File sinks to Serilog. And apart from it, we will also add some other configurations for the File sink in the Serilog.WriteTo configuration section. We can even configure the output path for all log files with the naming format.
{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "Serilog": {
        "Using": ["Serilog.Sinks.File", "Serilog.Sinks.Console"],
        "MinimumLevel": {
            "Default": "Information",
            "Override": {
                "Microsoft": "Warning",
                "System": "Warning"
            }
        },
        "WriteTo": [{
            "Name": "Console"
        }, {
            "Name": "File",
            "Args": {
                "path": "/logs/log-.txt",
                "rollOnFileSizeLimit": true,
                "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter,Serilog.Formatting.Compact",
                "rollingInterval": "Day"
            }
        }],
        "Enrich": ["FromLogContext", "WithThreadId", "WithMachineName"]
    }
}

Now go to Controller class and put any log you want to track. In my case, I kept below log information,

public WeatherForecastController(ILogger < WeatherForecastController > logger) {
        _logger = logger;
        _logger.LogInformation("WeatherForecast controller called ");
    }
    [HttpGet(Name = "GetWeatherForecast")]
public IEnumerable < WeatherForecast > Get() {
    _logger.LogInformation("WeatherForecast get method Starting.");
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    }).ToArray();
}

Now run your application and call the weatherForecast controller to get the method in swagger.
Once you hit the get method in swagger, you go to the file path ( this path we define in the appsettings.json -> write to -> file Path section), where we log it. In my case, it is in C: directory -> log folder -> log.txt

Now open this file you will see your log information is logged successfully in the file and console.

Since we have given in the appsettings.json file where we want to log the information, it is logging in 2 places.

Output is below


 

Serilog Structured Logging in JSON Format
Previously we added logs into a text file. Now if you want to log the information in JSON format, then we need to change the file format, and other things will automatically take care like below:

Now run your application again and call the weather forecast controller to get the method. Then go to the same directory path, a new JSON file you will see and you can find your logging information there as well.

In this article, we've seen the structured logging setup with the help of Serilog in ASP.NET Core 7.0. Logging into an application help developer while debugging an application, and Serilog helps us more with it.

HostForLIFE.eu ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.

 



European ASP.NET Core Hosting - HostForLIFE :: How To Implement Serilog In ASP.NET Core Web API?

clock March 6, 2023 07:20 by author Peter

SeriLog is one of developers' most popular and widely used logging libraries for .NET applications. It offers several benefits compared to other logging libraries available on the market.

What is Logging?
Logging is an essential part of any application. It helps developers understand what's happening in the application, diagnose, and troubleshoot problems.
Why do you need to consider Serilog instead of other logging libraries?

Scalability and Performance

Serilog is designed to handle large volumes of log data efficiently. Serilog is well optimized for performance. It was also designed with minimal impact on the application's performance while logging through features such as asynchronous logging, lazy message generation, and support for log message batching.

Extensibility
Serilog is highly extensible, and it has more than 90+ provided sinks, including popular logging services such as
    Amazon DynamoDB
    Azure Blob Storage
    Azure CosmosDB
    ElasticSearch
    Google Cloud StackDriver
    MariaDB
    MongoDB
    PostgreSQL
    SQLite
    SQL Server and so on

Serilog provides an advanced plug-in architecture that allows developers to extend the library with custom sinks and enrichers. It includes general-purpose sinks such as file and console sinks. In the following example, you will learn how to log messages in a file and on the console.

You can see more details about provided sinks in Serilog here.

Flexible and structured logging
Serilog provides a simple and flexible logging API that allows us to capture structured logs in various formats, including JSON, plain text, and XML.

It helps you and your team analyze the application, and you can use that data to make a more optimized version of your application.

Integration with popular .NET frameworks:
Serilog is most popularly used within the .NET community, and it has built-in integration with popular .NET frameworks, such as ASP.NET Core, Entity Framework, and Microsoft.Extensions.Logging.

Community Support:
Serilog is backed by a large community of active developers who contribute to the development of the framework. They are always ready to share knowledge and provide support. You can see more about it in Github repos here.

How to Configure the Serilog
In this article, I'm giving two examples. The First example shows how to log the information to a text file using File Sink, and the second example shows how to log the information to Console and a Text file using a separate configuration JSON file for Serilog with the help of Console and File Sinks.

You can clone My Github Repository to try this example here

Open Visual Studio Application → create a new project → select ASP.NET Core Web API Template → Give a name for your Project → Also make sure .NET 6.0 is selected in Framework dropdown additional information page → Click Create

After the project is created, try to run the project → Debug or Press F5. It will open a Swagger API Web Page in the New Browser window, and you will see the console like in the image below.

How to install Serilog dependencies?
You need to install dependencies before implementing the code. You can do this in two ways one is through GUI, and another one is through Package Manager Console.

In Solution Explorer Window → your project name → Dependencies → right-click Packages → Manage NuGet Packages.
It opens the NuGet package manager in that browse tab → search for Serilog.AspNet.Core → install that package as shown in the image below.

Package Manager Console
You can also install dependencies through Package Manager Console. To do this, click Tools → NuGet Package Manager → Package Manager Console. It brings the console and executes the following commands to install Serilog dependencies.
Install-Package Serilog
Install-Package Serilog.Sinks.Console
Install-Package Serilog.Sinks.File

Log the information to a text file using the serilog file sink

I will log the information to a text file using the serilog file sink in this first example.

Open program.cs file. Change the code in that file, which is given below.
using Serilog;

var builder = WebApplication.CreateBuilder(args);


// Add services to the container.

var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .CreateLogger();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);
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();


Open appsettings.json, and Change the code in that file, which is given below.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "../logs/webapi-.log",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {CorrelationId} {Level:u3} {Username} {Message:lj}{Exception}{NewLine}"
        }
      }
    ]
  }
}


In the Solution Explorer window → Controllers → WeatherForecastController.cs. Replace the following code.
using Microsoft.AspNetCore.Mvc;

namespace SerilogTutorial.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {

            _logger.LogInformation("Seri Log is Working");

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}


Debug the project Press F5.

It opens a SwaggerAPI Page in the browser, try to hit the GetWeatherForecast Endpoint.

It creates a logs folder in your Project folder and a webapi txt file inside it. If you open that webapi txt file, it contains the project log.

 

How to configure Serilog Properties in a separate JSON file instead of an appsettings.json file?
In this example, It will log information in the file and console using a separate configuration file for Serilog properties. If you want to try the second example, please follow the steps of the first example. It contains the basic configuration of the Serilog, or you can clone my Github repo using the link given above.

You need to add one JSON file for this scenario. In the Solution explorer window → right-click the project name → add → New Item → select JSON file. I added a JSON file name as seri-log.config.json.

I added the code in the given code snippet. Paste this code into that file
{
  "Serilog": {
    "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "../logs/webapi-.log",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {CorrelationId} {Level:u3} {Username} {Message:lj}{Exception}{NewLine}"
        }
      },
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message}{NewLine}{Exception}"
        }
      }
    ]
  }
}


It has two sinks one is a file that will log the information in a text file, and another is a console that will log the information in the console.

Also, you need to change the program.cs file code that will read the configuration from the newly created json file. That code is also given in the code snippet.
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

//logger

var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(new ConfigurationBuilder()
    .AddJsonFile("seri-log.config.json")
    .Build())
    .Enrich.FromLogContext()
    .CreateLogger();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);

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

After following the steps, press F5

Now the log information shows in the console, too.


Methods and Serilog Properties Explanations
new LoggerConfiguration() - This method creates a new LoggerConfiguration object that will be used to configure the Serilog logger.

ReadFrom.Configuration(builder.Configuration) - This method configures the logger based on the application's configuration. This method uses the builder.Configuration object to read the configuration settings and apply them to the logger.

Enrich.FromLogContext() - This method adds contextual information to log events. This will allow log events to be enriched with additional information, such as the name of the current method or the user that initiated the event.

CreateLogger() - This method is used to create the Serilog logger.
builder.Logging.ClearProviders() - It can be called on an instance of ILoggerFactory in a .NET application to remove all the logging providers from the logging pipeline.

builder.Logging.AddSerilog(logger) - This method is used to add a Serilog logger to the logging pipeline of a .NET application.

AddJsonFile("seri-log.config.json") - Configuration object that reads from a JSON file named "seri-log.config.json" (using the AddJsonFile() method) and then passes that configuration object to the ReadFrom.Configuration() method.

Build() - After loading the configuration file, it can be built into a Configuration object by calling the Build().

Using: This property is an array of strings specifying the Serilog packages the logger will use.

MinimumLevel: This is an important property you must remember while configuring the Serilog. This property specifies the minimum logging level for the logger. Serilog has 6 level minimum levels.
    Verbose: It is used to track the low level of severity. It is the noisiest level. It is rarely enabled for a production app.
    Debug: Debug is mostly used by the developer team to know about your application's internal events that are not necessarily observable from the outside.
    Information: Information is used for logging the general information on how the flow happens in your application.
    Warning: Warning is used for logging the information not needed to take immediate action now, but those may need attention future.
    Error: Error is used for logging functionality not working or broken. These types need immediate attention.
    Fatal: Fatal logs critical level that will cause an application crash. This types also need immediate attention.

Example: If you choose a higher level, it will log the lower level by default. If you choose minimum level as Warning, it logs warning, information, Debug, and Verbose level Details.

WriteTo: This property is an array of objects that specifies the log sinks to which the log events will be written.

Name: This property specifies the name of the sink.

Path: the property is used to specify the path to the log file that the File sink will write.

RollingInterval - property is used to specify how often to create a new log file. The log file name will include the date in the format specified by the rollingInterval property, with the specified file path and extension.

OutputTemplate - property accepts a string value that can include placeholders for various information associated with each log event. It contains the following placeholders in this example,

{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}: The date and time of the log event, in the format yyyy-MM-dd HH:mm:ss.fff zzz.

{CorrelationId}: A unique identifier for the request or operation associated with the log event, if one is available.

{Level:u3}: The log level of the event, abbreviated to three characters.

{Username}: The username associated with the request or operation, if one is available.

{Message:lj}: The log message associated with the event is left-justified and trimmed to fit within the maximum message length.

{Exception}: Any exception that is associated with the log event.

{NewLine}: A new line character.

Serilog is easy to use and configure and implement the structured logging in your .net application. It provides a wide variety of sinks that allow us to log your application information based on your needs easily. You will understand your application's events, errors, and performance metrics based on that information. In hard times it helps to troubleshoot application issues. If you are looking for a good logging framework, it's worth giving it a try to Serilog.



About HostForLIFE

HostForLIFE is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2019 Hosting, ASP.NET 5 Hosting, ASP.NET MVC 6 Hosting and SQL 2019 Hosting.


Month List

Tag cloud

Sign in