برچسب: Code4IT

  • 8 things about Records in C# you probably didn’t know | Code4IT

    8 things about Records in C# you probably didn’t know | Code4IT


    C# recently introduced Records, a new way of defining types. In this article, we will see 8 things you probably didn’t know about C# Records

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Records are the new data type introduced in 2021 with C# 9 and .NET Core 5.

    public record Person(string Name, int Id);
    

    Records are the third way of defining data types in C#; the other two are class and struct.

    Since they’re a quite new idea in .NET, we should spend some time experimenting with it and trying to understand its possibilities and functionalities.

    In this article, we will see 8 properties of Records that you should know before using it, to get the best out of this new data type.

    1- Records are immutable

    By default, Records are immutable. This means that, once you’ve created one instance, you cannot modify any of its fields:

    var me = new Person("Davide", 1);
    me.Name = "AnotherMe"; // won't compile!
    

    This operation is not legit.

    Even the compiler complains:

    Init-only property or indexer ‘Person.Name’ can only be assigned in an object initializer, or on ’this’ or ‘base’ in an instance constructor or an ‘init’ accessor.

    2- Records implement equality

    The other main property of Records is that they implement equality out-of-the-box.

    [Test]
    public void EquivalentInstances_AreEqual()
    {
        var me = new Person("Davide", 1);
        var anotherMe = new Person("Davide", 1);
    
        Assert.That(anotherMe, Is.EqualTo(me));
        Assert.That(me, Is.Not.SameAs(anotherMe));
    }
    

    As you can see, I’ve created two instances of Person with the same fields. They are considered equal, but they are not the same instance.

    3- Records can be cloned or updated using ‘with’

    Ok, so if we need to update the field of a Record, what can we do?

    We can use the with keyword:

    [Test]
    public void WithProperty_CreatesNewInstance()
    {
        var me = new Person("Davide", 1);
        var anotherMe = me with { Id = 2 };
    
        Assert.That(anotherMe, Is.Not.EqualTo(me));
        Assert.That(me, Is.Not.SameAs(anotherMe));
    }
    

    Take a look at me with { Id = 2 }: that operation creates a clone of me and updates the Id field.

    Of course, you can use with to create a new instance identical to the original one.

    [Test]
    public void With_CreatesNewInstance()
    {
        var me = new Person("Davide", 1);
    
        var anotherMe = me with { };
    
        Assert.That(anotherMe, Is.EqualTo(me));
        Assert.That(me, Is.Not.SameAs(anotherMe));
    }
    

    4- Records can be structs and classes

    Basically, Records act as Classes.

    public record Person(string Name, int Id);
    

    Sometimes that’s not what you want. Since C# 10 you can declare Records as Structs:

    public record struct Point(int X, int Y);
    

    Clearly, everything we’ve seen before is still valid.

    [Test]
    public void EquivalentStructsInstances_AreEqual()
    {
        var a = new Point(2, 1);
        var b = new Point(2, 1);
    
        Assert.That(b, Is.EqualTo(a));
        //Assert.That(a, Is.Not.SameAs(b));// does not compile!
    }
    

    Well, almost everything: you cannot use Is.SameAs() because, since structs are value types, two values will always be distinct values. You’ll get notified about it by the compiler, with an error that says:

    The SameAs constraint always fails on value types as the actual and the expected value cannot be the same reference

    5- Records are actually not immutable

    We’ve seen that you cannot update existing Records. Well, that’s not totally correct.

    That assertion is true in the case of “simple” Records like Person:

    public record Person(string Name, int Id);
    

    But things change when we use another way of defining Records:

    public record Pair
    {
        public Pair(string Key, string Value)
        {
            this.Key = Key;
            this.Value = Value;
        }
    
        public string Key { get; set; }
        public string Value { get; set; }
    }
    

    We can explicitly declare the properties of the Record to make it look more like plain classes.

    Using this approach, we still can use the auto-equality functionality of Records

    [Test]
    public void ComplexRecordsAreEquatable()
    {
        var a = new Pair("Capital", "Roma");
        var b = new Pair("Capital", "Roma");
    
        Assert.That(b, Is.EqualTo(a));
    }
    

    But we can update a single field without creating a brand new instance:

    [Test]
    public void ComplexRecordsAreNotImmutable()
    {
        var b = new Pair("Capital", "Roma");
        b.Value = "Torino";
    
        Assert.That(b.Value, Is.EqualTo("Torino"));
    }
    

    Also, only simple types are immutable, even with the basic Record definition.

    The ComplexPair type is a Record that accepts in the definition a list of strings.

    public record ComplexPair(string Key, string Value, List<string> Metadata);
    

    That list of strings is not immutable: you can add and remove items as you wish:

    [Test]
    public void ComplexRecordsAreNotImmutable2()
    {
        var b = new ComplexPair("Capital", "Roma", new List<string> { "City" });
        b.Metadata.Add("Another Value");
    
        Assert.That(b.Metadata.Count, Is.EqualTo(2));
    }
    

    In the example below, you can see that I added a new item to the Metadata list without creating a new object.

    6- Records can have subtypes

    A neat feature is that we can create a hierarchy of Records in a very simple manner.

    Do you remember the Person definition?

    public record Person(string Name, int Id);
    

    Well, you can define a subtype just as you would do with plain classes:

    public record Employee(string Name, int Id, string Role) : Person(Name, Id);
    

    Of course, all the rules of Boxing and Unboxing are still valid.

    [Test]
    public void Records_CanHaveSubtypes()
    {
        Person meEmp = new Employee("Davide", 1, "Chief");
    
        Assert.That(meEmp, Is.AssignableTo<Employee>());
        Assert.That(meEmp, Is.AssignableTo<Person>());
    }
    

    7- Records can be abstract

    …and yes, we can have Abstract Records!

    public abstract record Box(int Volume, string Material);
    

    This means that we cannot instantiate new Records whose type is marked ad Abstract.

    var box = new Box(2, "Glass"); // cannot create it, it's abstract
    

    On the contrary, we need to create concrete types to instantiate new objects:

    public record PlasticBox(int Volume) : Box(Volume, "Plastic");
    

    Again, all the rules we already know are still valid.

    [Test]
    public void Records_CanBeAbstract()
    {
        var plasticBox = new PlasticBox(2);
    
        Assert.That(plasticBox, Is.AssignableTo<Box>());
        Assert.That(plasticBox, Is.AssignableTo<PlasticBox>());
    }
    

    8- Record can be sealed

    Finally, Records can be marked as Sealed.

    public sealed record Point3D(int X, int Y, int Z);
    

    Marking a Record as Sealed means that we cannot declare subtypes.

    public record ColoredPoint3D(int X, int Y, int Z, string RgbColor) : Point3D(X, Y, X); // Will not compile!
    

    This can be useful when exposing your types to external systems.

    This article first appeared on Code4IT

    Additional resources

    As usual, a few links you might want to read to learn more about Records in C#.

    The first one is a tutorial from the Microsoft website that teaches you the basics of Records:

    🔗 Create record types | Microsoft Docs

    The second one is a splendid article by Gary Woodfine where he explores the internals of C# Records, and more:

    🔗C# Records – The good, bad & ugly | Gary Woodfine.

    Finally, if you’re interested in trivia about C# stuff we use but we rarely explore, here’s an article I wrote a while ago about GUIDs in C# – you’ll find some neat stuff in there!

    🔗5 things you didn’t know about Guid in C# | Code4IT

    Wrapping up

    In this article, we’ve seen 8 things you probably didn’t know about Records in C#.

    Records are quite new in the .NET ecosystem, so we can expect more updates and functionalities.

    Is there anything else we should add? Or maybe something you did not expect?

    Happy coding!

    🐧



    Source link

  • How to improve Serilog logging in .NET 6 by using Scopes &vert; Code4IT

    How to improve Serilog logging in .NET 6 by using Scopes | Code4IT


    Logs are important. Properly structured logs can be the key to resolving some critical issues. With Serilog’s Scopes, you can enrich your logs with info about the context where they happened.

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Even though it’s not one of the first things we usually set up when creating a new application, logging is a real game-changer in the long run.

    When an error occurred, if we have proper logging we can get more info about the context where it happened so that we can easily identify the root cause.

    In this article, we will use Scopes, one of the functionalities of Serilog, to create better logs for our .NET 6 application. In particular, we’re going to create a .NET 6 API application in the form of Minimal APIs.

    We will also use Seq, just to show you the final result.

    Adding Serilog in our Minimal APIs

    We’ve already explained what Serilog and Seq are in a previous article.

    To summarize, Serilog is an open source .NET library for logging. One of the best features of Serilog is that messages are in the form of a template (called Structured Logs), and you can enrich the logs with some value automatically calculated, such as the method name or exception details.

    To add Serilog to your application, you simply have to run dotnet add package Serilog.AspNetCore.

    Since we’re using Minimal APIs, we don’t have the StartUp file anymore; instead, we will need to add it to the Program.cs file:

    builder.Host.UseSerilog((ctx, lc) => lc
        .WriteTo.Console() );
    

    Then, to create those logs, you will need to add a specific dependency in your classes:

    public class ItemsRepository : IItemsRepository
    {
        private readonly ILogger<ItemsRepository> _logger;
    
        public ItemsRepository(ILogger<ItemsRepository> logger)
        {
            _logger = logger;
        }
    }
    

    As you can see, we’re injecting an ILogger<ItemsRepository>: specifying the related class automatically adds some more context to the logs that we will generate.

    Installing Seq and adding it as a Sink

    Seq is a logging platform that is a perfect fit for Serilog logs. If you don’t have it already installed, head to their download page and install it locally (you can even install it as a Docker container 🤩).

    In the installation wizard, you can select the HTTP port that will expose its UI. Once everything is in place, you can open that page on your localhost and see a page like this:

    Seq empty page on localhost

    On this page, we will see all the logs we write.

    But wait! ⚠ We still have to add Seq as a sink for Serilog.

    A sink is nothing but a destination for the logs. When using .NET APIs we can define our sinks both on the appsettings.json file and on the Program.cs file. We will use the second approach.

    First of all, you will need to install a NuGet package to add Seq as a sink: dotnet add package Serilog.Sinks.Seq.

    Then, you have to update the Serilog definition we’ve seen before by adding a .WriteTo.Seq instruction:

    builder.Host.UseSerilog((ctx, lc) => lc
        .WriteTo.Console()
        .WriteTo.Seq("http://localhost:5341")
        );
    

    Notice that we’ve specified also the port that exposes our Seq instance.

    Now, every time we log something, we will see our logs both on the Console and on Seq.

    How to add scopes

    The time has come: we can finally learn how to add Scopes using Serilog!

    Setting up the example

    For this example, I’ve created a simple controller, ItemsController, which exposes two endpoints: Get and Add. With these two endpoints, we are able to add and retrieve items stored in an in-memory collection.

    This class has 2 main dependencies: IItemsRepository and IUsersItemsRepository. Each of these interfaces has its own concrete class, each with a private logger injected in the constructor:

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

    and, similarly

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

    How do those classes use their own _logger instances?

    For example, the UsersItemRepository class exposes an AddItem method that adds a specific item to the list of items already possessed by a specific user.

    public void AddItem(string username, Item item)
    {
        if (!_usersItems.ContainsKey(username))
        {
            _usersItems.Add(username, new List<Item>());
            _logger.LogInformation("User was missing from the list. Just added");
        }
        _usersItems[username].Add(item);
        _logger.LogInformation("Added item for to the user's catalogue");
    }
    

    We are logging some messages, such as “User was missing from the list. Just added”.

    Something similar happens in the ItemsRepository class, where we have a GetItem method that returns the required item if it exists, and null otherwise.

    public Item GetItem(int itemId)
    {
        _logger.LogInformation("Retrieving item {ItemId}", itemId);
        return _allItems.FirstOrDefault(i => i.Id == itemId);
    }
    

    Finally, who’s gonna call these methods?

    [HttpPost(Name = "AddItems")]
    public IActionResult Add(string userName, int itemId)
    {
        var item = _itemsRepository.GetItem(itemId);
    
        if (item == null)
        {
            _logger.LogWarning("Item does not exist");
    
            return NotFound();
        }
        _usersItemsRepository.AddItem(userName, item);
    
        return Ok(item);
    }
    

    Ok then, we’re ready to run the application and see the result.

    When I call that endpoint by passing “davide” as userName and “1” as itemId, we can see these logs:

    Simple logging on Seq

    We can see the 3 log messages but they are unrelated one each other. In fact, if we expand the logs to see the actual values we’ve logged, we can see that only the “Retrieving item 1” log has some information about the item ID we want to associate with the user.

    Expanding logs on Seq

    Using BeginScope with Serilog

    Finally, it’s time to define the Scope.

    It’s as easy as adding a simple using statement; see how I added the scope to the Add method in the Controller:

    [HttpPost(Name = "AddItems")]
    public IActionResult Add(string userName, int itemId)
    {
        using (_logger.BeginScope("Adding item {ItemId} for user {UserName}", itemId, userName))
        {
            var item = _itemsRepository.GetItem(itemId);
    
            if (item == null)
            {
                _logger.LogWarning("Item does not exist");
    
                return NotFound();
            }
            _usersItemsRepository.AddItem(userName, item);
    
            return Ok(item);
        }
    }
    

    Here’s the key!

    using (_logger.BeginScope("Adding item {ItemId} for user {UserName}", itemId, userName))
    

    With this single instruction, we are actually performing 2 operations:

    1. we are adding a Scope to each message – “Adding item 1 for user davide”
    2. we are adding ItemId and UserName to each log entry that falls in this block, in every method in the method chain.

    Let’s run the application again, and we will see this result:

    Expanded logs on Seq with Scopes

    So, now you can use these new properties to get some info about the context of when this log happened, and you can use the ItemId and UserName fields to search for other related logs.

    You can also nest scopes, of course.

    Why scopes instead of Correlation ID?

    You might be thinking

    Why can’t I just use correlation IDs?

    Well, the answer is pretty simple: correlation IDs are meant to correlate different logs in a specific request, and, often, across services. You generally use Correlation IDs that represent a specific call to your API and act as a Request ID.

    For sure, that can be useful. But, sometimes, not enough.

    Using scopes you can also “correlate” distinct HTTP requests that have something in common.

    If I call 2 times the AddItem endpoint, I can filter both for UserName and for ItemId and see all the related logs across distinct HTTP calls.

    Let’s see a real example: I have called the endpoint with different values

    • id=1, username=“davide”
    • id=1, username=“luigi”
    • id=2, username=“luigi”

    Since the scope reference both properties, we can filter for UserName and discover that Luigi has added both Item1 and Item 2.

    Filtering logs by UserName

    At the same time, we can filter by ItemId and discover that the item with id = 2 has been added only once.

    Filtering logs by ItemId

    Ok, then, in the end, Scopes or Correlation IDs? The answer is simple:

    Both is good

    This article first appeared on Code4IT

    Read more

    As always, the best place to find the info about a library is its documentation.

    🔗 Serilog website

    If you prefer some more practical articles, I’ve already written one to help you get started with Serilog and Seq (and with Structured Logs):

    🔗 Logging with Serilog and Seq | Code4IT

    as well as one about adding Serilog to Console applications (which is slightly different from adding Serilog to .NET APIs)

    🔗 How to add logs on Console with .NET Core and Serilog | Code4IT

    Then, you might want to deep dive into Serilog’s BeginScope. Here’s a neat article by Nicholas Blumhardt. Also, have a look at the comments, you’ll find interesting points to consider

    🔗 The semantics of ILogger.BeginScope | Nicholas Blumhardt

    Finally, two must-read articles about logging best practices.

    The first one is by Thiago Nascimento Figueiredo:

    🔗 Logs – Why, good practices, and recommendations | Dev.to

    and the second one is by Llron Tal:

    🔗 9 Logging Best Practices Based on Hands-on Experience | Loom Systems

    Wrapping up

    In this article, we’ve added Scopes to our logs to enrich them with some common fields that can be useful to investigate in case of errors.

    Remember to read the last 3 links I’ve shared above, they’re pure gold – you’ll thank me later 😎

    Happy coding!

    🐧



    Source link

  • Avoid subtle duplication of code and logic &vert; Code4IT

    Avoid subtle duplication of code and logic | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Duplication is not only about lines of code, but also about data usage and meaning.
    Reducing it will help us minimize the impact of every change.

    Take this class as an example:

    class BookShelf
    {
        private Book[] myBooks = new Book[]
        {
             new Book(1, "C# in depth"),
             new Book(2, "I promessi paperi")
        };
    
        public int Count() => myBooks.Length;
        public bool IsEmpty() => myBooks.Length == 0;
        public bool HasElements() => myBooks.Length > 0;
    }
    

    Here, both Count and IsEmpty use the same logical way to check the length of the collection: by calling myBooks.Length.

    What happens if you have to change the myBooks collection and replace the array of Books with a collection that does not expose the Length property? You will have to replace the logic everywhere!

    So, a better approach is to “centralize” the way to count the items in the collection in this way:

    class BookShelf
    {
        private Book[] myBooks = new Book[]
        {
             new Book(1, "C# in depth"),
             new Book(2, "I promessi paperi")
        };
    
        public int Count() => myBooks.Length;
        public bool IsEmpty() => Count() == 0;
        public bool HasElements() => Count() > 0;
    }
    

    If you will need to replace the myBooks data type, you will simply have to update the Count method – everything else will be the same.

    Also, HasElements and IsEmpty are a logical duplication. If they’re not necessary, you should remove one. Remove the one most used in its negative form: if you find lots of if(!HasElements()), you should consider replacing it with if(IsEmpty()): always prefer the positive form!

    Yes, I know, this is an extreme example: it’s too simple. But think of a more complex class or data flow in which you reuse the same logical flow, even if you’re not really using the exact same lines of code.

    By duplicating the logic, you will need to write more tests that do the same thing. Also, it may happen that if you found a flaw in your logic, and you fix it in some places and forget to fix it in other methods.

    Centralizing it will allow you to build safer code that is easier to test and update.

    A simple way to avoid “logical” duplication? Abstract classes!

    Well, there are many others… that I expect you to tell me in the comments section!

    Happy coding!

    🐧



    Source link

  • SelectMany in LINQ &vert; Code4IT

    SelectMany in LINQ | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    There’s one LINQ method that I always struggle in understanding: SelectMany.

    It’s actually a pretty simple method, but somehow it doesn’t stuck in my head.

    In simple words, SelectMany works on collections of items that you can use, in whichever way, to retrieve other items.

    Let’s see an example using the dear old for loop, and then we will replace it with SelectMany.

    For this example, I’ve created a simple record type that represents an office. Each office has one or more phone numbers.

    record Office(string Place, string[] PhoneNumbers);
    

    Now, our company has a list of offices.

    List<Office> myCompanyOffices = new List<Office>{
        new Office("Turin", new string[]{"011-1234567", "011-2345678", "011-34567890"}),
        new Office("Rome", new string[]{"031-4567", "031-5678", "031-890"}),
        new Office("Dublin", new string[]{"555-031-4567", "555-031-5678", "555-031-890"}),
    };
    

    How can we retrieve the list of all phone numbers?

    Iterating with a FOR-EACH loop

    The most obvious way is to iterate over the collection with a for or a foreach loop.

    List<string> allPhoneNumbers = new List<string>();
    
    foreach (var office in myCompanyOffices)
    {
        allPhoneNumbers.AddRange(office.PhoneNumbers);
    }
    

    Nothing fancy: we use AddRange instead of Add, just to avoid another inner loop.

    Using SelectMany

    You can do the same thing in a single line using LINQ’s SelectMany.

    List<string> allPhoneNumbers = myCompanyOffices.SelectMany(b => b.PhoneNumbers).ToList();
    

    This method aggregates all the PhoneNumbers elements in an IEnumerable<string> instance (but then we need to call ToList to convert it).

    Of course, always check that the PhoneNumbers list is not null, otherwise it will throw an ArgumentNullException.

    The simplest way is by using the ?? operator:

    allPhoneNumbers = myCompanyOffices.SelectMany(b => b.PhoneNumbers ?? Enumerable.Empty<string>()).ToList();
    

    Wrapping up

    Easy, right? I don’t have anything more to add!

    Happy coding!

    🐧



    Source link

  • 5 tricks every C# dev should know about LINQPad &vert; Code4IT

    5 tricks every C# dev should know about LINQPad | Code4IT


    LINQPad is one of the tools I use daily. But still, I haven’t used it at its full power. And you?

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    LINQPad is one of my best friends: I use it daily, and it helps me A LOT when I need to run some throwaway code.

    There are many other tools out there, but I think that LINQPad (well, the full version!) is one of the best tools on the market.

    But still, many C# developers only use just a few of its functionalities! In this article, I will show you my top 5 functionalities you should know.

    Advanced Dump()

    As many of you already know, to print stuff on the console you don’t have to call Console.WriteLine(something), but you can use something.Dump();

    void Main()
    {
        var user = new User(1, "Davide", "DavideB");
        user.Dump();
    }
    

    Basic usage of Dump()

    You can simplify it by avoiding calling the Dump operation in a separate step: Dump can print the content and return it at the same time:

    var user = new User(1, "Davide", "DavideB").Dump();
    

    Dump() can both print and return a value

    For sure, this simple trick makes your code easier to read!

    Ok, what if you have too many Dump calls and you don’t know which operation prints which log? Lucky for us, the Dump method accepts a string as a Title: that text will be displayed in the output panel.

    var user = new User(1, "Davide", "DavideB").Dump("My User content");
    

    You can now see the “My User content” header right above the log of the user:

    Dump() with title

    Dump containers

    We can do a step further and introduce Dump containers.

    Dump Containers are some sort of sink for your logs (we’ve already talked about sinks, do you remember?). Once you’ve instantiated a DumpContainer object, you can perform some operations such as AppendContent to append some content at the end of the logs, ClearContent to clear the content (obviously!), and Dump to display the content of the Container in the Results panel.

    DumpContainer dc = new DumpContainer();
    
    dc.Content = "Hey!";
    dc.AppendContent("There");
    
    dc.Dump();
    

    Note: you don’t need to place the Dump() instruction at the end of the script: you can put it at the beginning and you’ll see the content as soon as it gets added. Otherwise, you will build the internal list of content and display it only at the end.

    So, this is perfectly valid:

    DumpContainer dc = new DumpContainer();
    dc.Dump();
    
    
    dc.Content = "Hey!";
    dc.AppendContent("There");
    

    Simple usage of Dump container

    You can even explicitly set the content of the Container: setting it will replace everything else.

    Here you can see what happens when we override the content:

    Replace log content with DumpContainer

    Why should we even care? 🤔

    My dear friend, it’s easy! Because we can create more Containers to log different things!

    Take this example: we want to loop over a list of items and use one Container to display the item itself, and another Container to list what happens when we perform some operations on each item. Yeeees, I know, it’s hard to understand in this way: let me show you an example!

    DumpContainer dc1 = new DumpContainer();
    DumpContainer dc2 = new DumpContainer();
    
    dc1.Dump();
    dc2.Dump();
    
    var users = new List<User> {
        new User(1, "Davide", "DavideB"),
        new User(2, "Dav", "Davi Alt"),
        new User(3, "Bellone", "Bellone 3"),
    };
    
    foreach (var element in users)
    {
        dc1.AppendContent(element);
        dc2.AppendContent(element.name.ToUpper());
    }
    

    Here we’re using two different containers, each of them lives its own life.

    Using multiple containers

    In this example I used AppendContent, but of course, you can replace the full content of a Container to analyze one item at a time.

    I can hear you: there’s another question in your mind:

    How can we differentiate those containers?

    You can use the Style property of the DumpContainer class to style the output, using CSS-like properties:

    DumpContainer dc2 = new DumpContainer();
    dc2.Style = "color:red; font-weight: bold";
    

    Now all the content stored in the dc2 container will be printed in red:

    Syling DumpContainer with CSS rules

    Great stuff 🤩

    Read text from input

    Incredibly useful, but often overlooked, is the ability to provide inputs to our scripts.

    To do that, you can rely on the Util.ReadLine method already included in LINQPad:

    string myContent = Util.ReadLine();
    

    When running the application, you will see a black box at the bottom of the window that allows you to write (or paste) some text. That text will then be assigned to the myContent variable.

    Using input in LINQPad

    There’s a nice overload that allows you to specify a sort of title to the text box, to let you know which is the current step:

    Input boxes can have a title

    Paste as escaped string

    This is one of my favorite functionalities: many times I have to escape text that contains quotes, copied from somewhere else to assign it to a string variable; I used to lose time escaping those values manually (well, using other tools that still are slower than this one).

    Take this JSON:

    {
      "name": "davide",
      "gender": "male",
      "probability": 0.99,
      "count": 82957
    }
    

    Assigning it manually to a string becomes a mess. Lucky for us, we can copy it, get back on LINQPad, right-click, choose “Paste as escaped string” (or, if you prefer, use Alt+Shift+V) and have it already escaped and ready to be used:

    Escaped string in LINQPad

    That operation will generate this string:

    string content = "{\n\t\"name\": \"davide\",\n\t\"gender\": \"male\",\n\t\"probability\": 0.99,\n\t\"count\": 82957\n}";
    

    Not bad, isn’t it? 😎

    xUnit test support

    Another nice functionality that you can use to toy with classes or methods you don’t know is the xUnit test support.

    By clicking on the Query > Add XUnit Test Support, you can add xUnit to your query and write (and run, obviously) unit tests.

    All those tests are placed in a region named Tests:

    and can be run both by pressing Alt+Shift+T or by calling RunTests() in the Main method.

    After running the tests you will see a report with the list of the tests that passed and the details of the tests that failed:

    xUnit test result

    This article first appeared on Code4IT

    Wrapping up

    We’ve seen 5 amazing tricks to get the best out of LINQPad. In my opinion, every C# developer that uses this tool should know those tricks, they can really boost your productivity.

    Did you already know all of them? Which are your favorites? Drop a message in the comments section or on Twitter 📧

    Happy coding!

    🐧





    Source link

  • F.I.R.S.T. acronym for better unit tests &vert; Code4IT

    F.I.R.S.T. acronym for better unit tests | Code4IT


    Good unit tests have some properties in common: they are Fast, Independent, Repeatable, Self-validating, and Thorough. In a word: FIRST!

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    FIRST is an acronym that you should always remember if you want to write clean and extensible tests.

    This acronym tells us that Unit Tests should be Fast, Independent, Repeatable, Self-validating, and Thorough.

    Fast

    You should not create tests that require a long time for setup and start-up: ideally, you should be able to run the whole test suite in under a minute.

    If your unit tests are taking too much time for running, there must be something wrong with it; there are many possibilities:

    1. You’re trying to access remote sources (such as real APIs, Databases, and so on): you should mock those dependencies to make tests faster and to avoid accessing real resources. If you need real data, consider creating integration/e2e tests instead.
    2. Your system under test is too complex to build: too many dependencies? DIT value too high?
    3. The method under test does too many things. You should consider splitting it into separate, independent methods, and let the caller orchestrate the method invocations as necessary.

    Independent (or Isolated)

    Test methods should be independent of one another.

    Avoid doing something like this:

    MyObject myObj = null;
    
    [Fact]
    void Test1()
    {
        myObj = new MyObject();
        Assert.True(string.IsNullOrEmpty(myObj.MyProperty));
    
    }
    
    [Fact]
    void Test2()
    {
    
        myObj.MyProperty = "ciao";
        Assert.Equal("oaic", Reverse(myObj.MyProperty));
    
    }
    

    Here, to have Test2 working correctly, Test1 must run before it, otherwise myObj would be null. There’s a dependency between Test1 and Test2.

    How to avoid it? Create new instances for every test! May it be with some custom methods or in the StartUp phase. And remember to reset the mocks as well.

    Repeatable

    Unit Tests should be repeatable. This means that wherever and whenever you run them, they should behave correctly.

    So you should remove any dependency on the file system, current date, and so on.

    Take this test as an example:

    [Fact]
    void TestDate_DoNotDoIt()
    {
    
        DateTime d = DateTime.UtcNow;
        string dateAsString = d.ToString("yyyy-MM-dd");
    
        Assert.Equal("2022-07-19", dateAsString);
    }
    

    This test is strictly bound to the current date. So, if I’ll run this test again in a month, it will fail.

    We should instead remove that dependency and use dummy values or mock.

    [Fact]
    void TestDate_DoIt()
    {
    
        DateTime d = new DateTime(2022,7,19);
        string dateAsString = d.ToString("yyyy-MM-dd");
    
        Assert.Equal("2022-07-19", dateAsString);
    }
    

    There are many ways to inject DateTime (and other similar dependencies) with .NET. I’ve listed some of them in this article: “3 ways to inject DateTime and test it”.

    Self-validating

    Self-validating means that a test should perform operations and programmatically check for the result.

    For instance, if you’re testing that you’ve written something on a file, the test itself is in charge of checking that it worked correctly. No manual operations should be done.

    Also, tests should provide explicit feedback: a test either passes or fails; no in-between.

    Thorough

    Unit Tests should be thorough in that they must validate both the happy paths and the failing paths.

    So you should test your functions with valid inputs and with invalid inputs.

    You should also validate what happens if an exception is thrown while executing the path: are you handling errors correctly?

    Have a look at this class, with a single, simple, method:

    public class ItemsService
    {
    
        readonly IItemsRepository _itemsRepo;
    
        public ItemsService(IItemsRepository itemsRepo)
        {
            _itemsRepo = itemsRepo;
        }
    
        public IEnumerable<Item> GetItemsByCategory(string category, int maxItems)
        {
    
            var allItems = _itemsRepo.GetItems();
    
            return allItems
                    .Where(i => i.Category == category)
                    .Take(maxItems);
        }
    }
    

    Which tests should you write for GetItemsByCategory?

    I can think of these:

    • what if category is null or empty?
    • what if maxItems is less than 0?
    • what if allItems is null?
    • what if one of the items inside allItems is null?
    • what if _itemsRepo.GetItems() throws an exception?
    • what if _itemsRepo is null?

    As you can see, even for a trivial method like this you should write a lot of tests, to ensure that you haven’t missed anything.

    Conclusion

    F.I.R.S.T. is a good way to way to remember the properties of a good unit test suite.

    Always try to stick to it, and remember that tests should be written even better than production code.

    Happy coding!

    🐧



    Source link

  • use Miniprofiler instead of Stopwatch to profile code performance &vert; Code4IT

    use Miniprofiler instead of Stopwatch to profile code performance | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Do you need to tune up the performance of your code? You can create some StopWatch objects and store the execution times or rely on external libraries like MiniProfiler.

    Note: of course, we’re just talking about time duration, and not about memory usage!

    How to profile code using Stopwatch

    A Stopwatch object acts as a (guess what?) stopwatch.

    You can manually make it start and stop, and keep track of the elapsed time:

    Stopwatch sw = Stopwatch.StartNew();
    DoSomeOperations(100);
    var with100 = sw.ElapsedMilliseconds;
    
    
    sw.Restart();
    DoSomeOperations(2000);
    var with2000 = sw.ElapsedMilliseconds;
    
    sw.Stop();
    
    Console.WriteLine($"With 100: {with100}ms");
    Console.WriteLine($"With 2000: {with2000}ms");
    

    It’s useful, but you have to do it manually. There’s a better choice.

    How to profile code using MiniProfiler

    A good alternative is MiniProfiler: you can create a MiniProfiler object that holds all the info related to the current code execution. You then can add some Steps, which can have a name, and even nest them.

    Finally, you can print the result using RenderPlainText.

    MiniProfiler profiler = MiniProfiler.StartNew();
    
    using (profiler.Step("With 100"))
    {
        DoSomeOperations(100);
    }
    
    
    using (profiler.Step("With 2000"))
    {
        DoSomeOperations(2000);
    }
    
    Console.WriteLine(profiler.RenderPlainText());
    

    You won’t anymore stop and start any StopWatch instance.

    You can even use inline steps, to profile method execution and store its return value:

    var value = profiler.Inline(() => MethodThatReturnsSomething(12), "Get something");
    

    Here I decided to print the result on the Console. You can even create HTML reports, which are quite useful when profiling websites. You can read more here, where I experimented with MiniProfiler in a .NET API project.

    Here’s an example of what you can get:

    MiniProfiler API report

    Further readings

    We’ve actually already talked about MiniProfiler in an in-depth article you can find here:

    🔗 Profiling .NET code with MiniProfiler | Code4IT

    Which, oddly, is almost more detailed than the official documentation, that you can still find here:

    🔗 MiniProfiler for .NET | MiniProfiler

    Happy coding!

    🐧



    Source link

  • How to log Correlation IDs in .NET APIs with Serilog &vert; Code4IT

    How to log Correlation IDs in .NET APIs with Serilog | Code4IT


    APIs often call other APIs to perform operations. If an error occurs in one of them, how can you understand the context that caused that error? You can use Correlation IDs in your logs!

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Correlation IDs are values that are passed across different systems to correlate the operations performed during a “macro” operation.

    Most of the time they are passed as HTTP Headers – of course in systems that communicate via HTTP.

    In this article, we will learn how to log those Correlation IDs using Serilog, a popular library that helps handle logs in .NET applications.

    Setting up the demo dotNET project

    This article is heavily code-oriented. So, let me first describe the demo project.

    Overview of the project

    To demonstrate how to log Correlation IDs and how to correlate logs generated by different systems, I’ve created a simple solution that handles bookings for a trip.

    The “main” project, BookingSystem, fetches data from external systems by calling some HTTP endpoints; it then manipulates the data and returns an aggregate object to the caller.

    BookingSystem depends on two projects, placed within the same solution: CarRentalSystem, which returns data about the available cars in a specified date range, and HotelsSystem, which does the same for hotels.

    So, this is the data flow:

    Operations sequence diagram

    If an error occurs in any of those systems, can we understand the full story of the failed request? No. Unless we use Correlation IDs!

    Let’s see how to add them and how to log them.

    We need to propagate HTTP Headers. You could implement it from scratch, as we’ve seen in a previous article. Or we could use a native library that does it all for us.

    Of course, let’s go with the second approach.

    For every project that will propagate HTTP headers, we have to follow these steps.

    First, we need to install Microsoft.AspNetCore.HeaderPropagation: this NuGet package allows us to add the .NET classes needed to propagate HTTP headers.

    Next, we have to update the part of the project that we use to configure our application. For .NET projects with Minimal APIs, it’s the Program class.

    Here we need to add the capability to read the HTTP Context, by using

    builder.Services.AddHttpContextAccessor();
    

    As you can imagine, this is needed because, to propagate HTTP Headers, we need to know which are the incoming HTTP Headers. And they can be read from the HttpContext object.

    Next, we need to specify, as a generic behavior, which headers must be propagated. For instance, to propagate the “my-custom-correlation-id” header, you must add

    builder.Services.AddHeaderPropagation(options => options.Headers.Add("my-custom-correlation-id"));
    

    Then, for every HttpClient that will propagate those headers, you have to add AddHeaderPropagation(), like this:

    builder.Services.AddHttpClient("cars_system", c =>
        {
            c.BaseAddress = new Uri("https://localhost:7159/");
        }).AddHeaderPropagation();
    

    Finally, one last instruction that tells the application that it needs to use the Header Propagation functionality:

    app.UseHeaderPropagation();
    

    To summarize, here’s the minimal configuration to add HTTP Header propagation in a dotNET API.

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
    
        builder.Services.AddControllers();
        builder.Services.AddHttpContextAccessor();
        builder.Services.AddHeaderPropagation(options => options.Headers.Add("my-custom-correlation-id"));
    
        builder.Services.AddHttpClient("cars_system", c =>
        {
            c.BaseAddress = new Uri("https://localhost:7159/");
        }).AddHeaderPropagation();
    
        var app = builder.Build();
        app.UseHeaderPropagation();
        app.MapControllers();
        app.Run();
    }
    

    We’re almost ready to go!

    But we’re missing the central point of this article: logging an HTTP Header as a Correlation ID!

    Initializing Serilog

    We’ve already met Serilog several times in this blog, so I won’t repeat how to install it and how to define logs the best way possible.

    We will write our logs on Seq, and we’re overriding the minimum level to skip the noise generated by .NET:

    builder.Host.UseSerilog((ctx, lc) => lc
        .WriteTo.Seq("http://localhost:5341")
        .MinimumLevel.Information()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
        .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
        .Enrich.FromLogContext();
    

    Since you probably know what’s going on, let me go straight to the point.

    Install Serilog Enricher for Correlation IDs

    We’re gonna use a specific library to log HTTP Headers treating them as Correlation IDs. To use it, you have to install the Serilog.Enrichers.CorrelationId package available on NuGet.

    Therefore, you can simply run

    dotnet add Serilog.Enrichers.CorrelationId
    

    to every .NET project that will use this functionality.

    Once we have that NuGet package ready, we can add its functionality to our logger by adding this line:

    .Enrich.WithCorrelationIdHeader("my-custom-correlation-id")
    

    This simple line tells dotnet that, when we see an HTTP Header named “my-custom-correlation-id”, we should log it as a Correlation ID.

    Run it all together

    Now we have everything in place – it’s time to run it!

    We have to run all the 3 services at the same time (you can do it with VisualStudio or you can run them separately using a CMD), and we need to have Seq installed on our local machine.

    You will see 3 instances of Swagger, and each instance is running under a different port.

    Swagger pages of our systems

    Once we have all the 3 applications up and running, we can call the /Bookings endpoint passing it a date range and an HTTP Header with key “my-custom-correlation-id” and value = “123” (or whatever we want).

    How to use HTTP Headers with Postman

    If everything worked as expected, we can open Seq and see all the logs we’ve written in our applications:

    All logs in SEQ

    Open one of them and have a look at the attributes of the logs: you will see a CorrelationId field with the value set to “123”.

    Our HTTP Header is now treated as Correlation ID

    Now, to better demonstrate how it works, call the endpoint again, but this time set “789” as my-custom-correlation-id, and specify a different date range. You should be able to see another set of logs generated by this second call.

    You can now apply filters to see which logs are related to a specific Correlation ID: open one log, click on the tick button and select “Find”.

    Filter button on SEQ

    You will then see all and only logs that were generated during the call with header my-custom-correlation-id set to “789”.

    List of all logs related to a specific Correlation ID

    Further readings

    That’s it. With just a few lines of code, you can dramatically improve your logging strategy.

    You can download and run the whole demo here:

    🔗 LogCorrelationId demo | GitHub

    To run this project you have to install both Serilog and Seq. You can do that by following this step-by-step guide:

    🔗 Logging with Serilog and Seq | Code4IT

    For this article, we’ve used the Microsoft.AspNetCore.HeaderPropagation package, which is ready to use. Are you interested in building your own solution – or, at least, learning how you can do that?

    🔗 How to propagate HTTP Headers (and Correlation IDs) using HttpClients in C# | Code4IT

    Lastly, why not use Serilog’s Scopes? And what are they? Check it out here:

    🔗 How to improve Serilog logging in .NET 6 by using Scopes | Code4IT

    Wrapping up

    This article concludes a sort of imaginary path that taught us how to use Serilog, how to correlate different logs within the same application using Scopes, and how to correlate logs from different services using Correlation IDs.

    Using these capabilities, you will be able to write logs that can help you understand the context in which a specific log occurred, thus helping you fix errors more efficiently.

    This article first appeared on Code4IT

    Happy coding!

    🐧



    Source link

  • throw exceptions instead of returning null when there is no fallback &vert; Code4IT

    throw exceptions instead of returning null when there is no fallback | Code4IT


    In case of unmanageable error, should you return null or throw exceptions?

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    When you don’t have any fallback operation to manage null values (eg: retry pattern), you should throw an exception instead of returning null.

    You will clean up your code and make sure that, if something cannot be fixed, it gets caught as soon as possible.

    Don’t return null or false

    Returning nulls impacts the readability of your code. The same happens for boolean results for operations. And you still have to catch other exceptions.

    Take this example:

    bool SaveOnFileSystem(ApiItem item)
    {
        // save on file system
        return false;
    }
    
    ApiItem GetItemFromAPI(string apiId)
    {
        var httpResponse = GetItem(apiId);
        if (httpResponse.StatusCode == 200)
        {
            return httpResponse.Content;
        }
        else
        {
            return null;
        }
    }
    
    DbItem GetItemFromDB()
    {
        // returns the item or null
        return null;
    }
    

    If all those methods complete successfully, they return an object (DbItem, ApiItem, or true); if they fail, they return null or false.

    How can you consume those methods?

    void Main()
    {
        var itemFromDB = GetItemFromDB();
        if (itemFromDB != null)
        {
            var itemFromAPI = GetItemFromAPI(itemFromDB.ApiId);
    
            if (itemFromAPI != null)
            {
                bool successfullySaved = SaveOnFileSystem(itemFromAPI);.
    
                if (successfullySaved)
                    Console.WriteLine("Saved");
            }
        }
        Console.WriteLine("Cannot save the item");
    }
    

    Note that there is nothing we can do in case something fails. So, do we really need all that nesting? We can do better!

    Throw Exceptions instead

    Let’s throw exceptions instead:

    void SaveOnFileSystem(ApiItem item)
    {
        // save on file system
        throw new FileSystemException("Cannot save item on file system");
    }
    
    
    ApiItem GetItemFromAPI(string apiId)
    {
        var httpResponse = GetItem(apiId);
        if (httpResponse.StatusCode == 200)
        {
            return httpResponse.Content;
        }
        else
        {
            throw new ApiException("Cannot download item");
        }
    }
    
    
    DbItem GetItemFromDB()
    {
        // returns the item or throws an exception
        throw new DbException("item not found");
    }
    

    Here, each method can complete in two statuses: it either completes successfully or it throws an exception of a type that tells us about the operation that failed.

    We can then consume the methods in this way:

    void Main()
    {
        try
        {
            var itemFromDB = GetItemFromDB();
            var itemFromAPI = GetItemFromAPI(itemFromDB.ApiId);
            SaveOnFileSystem(itemFromAPI);
            Console.WriteLine("Saved");
        }
        catch(Exception ex)
        {
            Console.WriteLine("Cannot save the item");
        }
    
    }
    

    Now the reader does not have to spend time reading the nested operations, it’s all more linear and immediate.

    Conclusion

    Remember, this way of writing code should be used only when you cannot do anything if an operation failed. You should use exceptions carefully!

    Now, a question for you: if you need more statuses as a return type of those methods (so, not only “success” and “fail”, but also some other status like “partially succeeded”), how would you transform that code?

    Happy coding!

    🐧



    Source link

  • The 2 secret endpoints I create in my .NET APIs &vert; Code4IT

    The 2 secret endpoints I create in my .NET APIs | Code4IT


    In this article, I will show you two simple tricks that help me understand the deployment status of my .NET APIs

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    When I create Web APIs with .NET I usually add two “secret” endpoints that I can use to double-check the status of the deployment.

    I generally expose two endpoints: one that shows me some info about the current environment, and another one that lists all the application settings defined after the deployment.

    In this article, we will see how to create those two endpoints, how to update the values when building the application, and how to hide those endpoints.

    Project setup

    For this article, I will use a simple .NET 6 API project. We will use Minimal APIs, and we will use the appsettings.json file to load the application’s configuration values.

    Since we are using Minimal APIs, you will have the endpoints defined in the Main method within the Program class.

    To expose an endpoint that accepts the GET HTTP method, you can write

    endpoints.MapGet("say-hello", async context =>
    {
       await context.Response.WriteAsync("Hello, everybody!");
    });
    

    That’s all you need to know about .NET Minimal APIs for the sake of this article. Let’s move to the main topics ⏩

    How to show environment info in .NET APIs

    Let’s say that your code execution depends on the current Environment definition. Typical examples are that, if you’re running on production you may want to hide some endpoints otherwise visible in the other environments, or that you will use a different error page when an unhandled exception is thrown.

    Once the application has been deployed, how can you retrieve the info about the running environment?

    Here we go:

    app.MapGet("/env", async context =>
    {
        IWebHostEnvironment? hostEnvironment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
        var thisEnv = new
        {
            ApplicationName = hostEnvironment.ApplicationName,
            Environment = hostEnvironment.EnvironmentName,
        };
    
        var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
        await context.Response.WriteAsJsonAsync(thisEnv, jsonSerializerOptions);
    });
    

    This endpoint is quite simple.

    The context variable, which is of type HttpContext, exposes some properties. Among them, the RequestServices property allows us to retrieve the services that have been injected when starting up the application. We can then use GetRequiredService to get a service by its type and store it into a variable.

    💡 GetRequiredService throws an exception if the service cannot be found. On the contrary, GetService returns null. I usually prefer GetRequiredService, but, as always, it depends on what you’re using it.

    Then, we create an anonymous object with the information of our interest and finally return them as an indented JSON.

    It’s time to run it! Open a terminal, navigate to the API project folder (in my case, SecretEndpoint), and run dotnet run. The application will compile and start; you can then navigate to /env and see the default result:

    The JSON result of the env endpoint

    How to change the Environment value

    While the applicationName does not change – it is the name of the running assembly, so any other value will make stop your application from running – you can (and, maybe, want to) change the Environment value.

    When running the application using the command line, you can use the --environment flag to specify the Environment value.

    So, running

    dotnet run --environment MySplendidCustomEnvironment
    

    will produce this result:

    MySplendidCustomEnvironment is now set as Environment

    There’s another way to set the environment: update the launchSettings.json and run the application using Visual Studio.

    To do that, open the launchSettings.json file and update the profile you are using by specifying the Environment name. In my case, the current profile section will be something like this:

    "profiles": {
    "SecretEntpoints": {
        "commandName": "Project",
        "dotnetRunMessages": true,
        "launchBrowser": true,
        "launchUrl": "swagger",
        "applicationUrl": "https://localhost:7218;http://localhost:5218",
        "environmentVariables":
            {
                "ASPNETCORE_ENVIRONMENT": "EnvByProfile"
            }
        },
    }
    

    As you can see, the ASPNETCORE_ENVIRONMENT variable is set to EnvByProfile.

    If you run the application using Visual Studio using that profile you will see the following result:

    EnvByProfile as defined in the launchSettings file

    How to list all the configurations in .NET APIs

    In my current company, we deploy applications using CI/CD pipelines.

    This means that final variables definition comes from the sum of 3 sources:

    • the project’s appsettings file
    • the release pipeline
    • the deployment environment

    You can easily understand how difficult it is to debug those applications without knowing the exact values for the configurations. That’s why I came up with these endpoints.

    To print all the configurations, we’re gonna use an approach similar to the one we’ve used in the previous example.

    The endpoint will look like this:

    app.MapGet("/conf", async context =>
    {
        IConfiguration? allConfig = context.RequestServices.GetRequiredService<IConfiguration>();
    
        IEnumerable<KeyValuePair<string, string>> configKv = allConfig.AsEnumerable();
    
        var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
        await context.Response.WriteAsJsonAsync(configKv, jsonSerializerOptions);
    });
    

    What’s going on? We are retrieving the IConfiguration object, which contains all the configurations loaded at startup; then, we’re listing all the configurations as key-value pairs, and finally, we’re returning the list to the client.

    As an example, here’s my current appsettings.json file:

    {
      "ApiService": {
        "BaseUrl": "something",
        "MaxPage": 5,
        "NestedValues": {
          "Skip": 10,
          "Limit": 56
        }
      },
      "MyName": "Davide"
    }
    

    When I run the application and call the /conf endpoint, I will see the following result:

    Configurations printed as key-value pairs

    Notice how the structure of the configuration values changes. The value

    {
      "ApiService": {
        "NestedValues": {
          "Limit": 56
        }
    }
    

    is transformed into

    {
        "Key": "ApiService:NestedValues:Limit",
        "Value": "56"
    },
    

    That endpoint shows a lot more than you can imagine: take some time to have a look at those configurations – you’ll thank me later!

    How to change the value of a variable

    There are many ways to set the value of your variables.

    The most common one is by creating an environment-specific appsettings file that overrides some values.

    So, if your environment is called “EnvByProfile”, as we’ve defined in the previous example, the file will be named appsettings.EnvByProfile.json.

    There are actually some other ways to override application variables: we will learn them in the next article, so stay tuned! 😎

    3 ways to hide your endpoints from malicious eyes

    Ok then, we have our endpoints up and running, but they are visible to anyone who correctly guesses their addresses. And you don’t want to expose such sensitive info to malicious eyes, right?

    There are, at least, 3 simple values to hide those endpoints:

    • Use a non-guessable endpoint: you can use an existing word, such as “housekeeper”, use random letters, such as “lkfrmlvkpeo”, or use a Guid, such as “E8E9F141-6458-416E-8412-BCC1B43CCB24”;
    • Specify a key on query string: if that key is not found or it has an invalid value, return a 404-not found result
    • Use an HTTP header, and, again, return 404 if it is not valid.

    Both query strings and HTTP headers are available in the HttpContext object injected in the route definition.

    Now it’s your turn to find an appropriate way to hide these endpoints. How would you do that? Drop a comment below 📩

    Edit 2022-10-10: I thought it was quite obvious, but apparently it is not: these endpoints expose critical information about your applications and your infrastructure, so you should not expose them unless it is strictly necessary! If you have strong authentication in place, use it to secure those endpoints. If you don’t, hide those endpoints the best you can, and show only necessary data, and not everything. Strip out sensitive content. And, as soon as you don’t need that info anymore, remove those endpoints (comment them out or generate them only if a particular flag is set at compilation time). Another possible way is by using feature flags. In the end, take that example with a grain of salt: learn that you can expose them, but keep in mind that you should not expose them.

    Further readings

    We’ve used a quite new way to build and develop APIs with .NET, called “Minimal APIs”. You can read more here:

    🔗 Minimal APIs | Microsoft Learn

    If you are not using Minimal APIs, you still might want to create such endpoints. We’ve talked about accessing the HttpContext to get info about the HTTP headers and query string. When using Controllers, accessing the HttpContext requires some more steps. Here’s an article that you may find interesting:

    🔗 How to access the HttpContext in .NET API | Code4IT

    This article first appeared on Code4IT

    Wrapping up

    In this article, we’ve seen how two endpoints can help us with understanding the status of the deployments of our applications.

    It’s a simple trick that you can consider adding to your projects.

    Do you have some utility endpoints?

    Happy coding!

    🐧



    Source link