The first task that I want to tackle is the ability to read and save various settings from a file. This will make it easier for the user to make queries without having to add certain properties to the query. I won’t go into how to create a project and how to upload it to GitHub for time’s sake. That being said, I will show the project layout for demonstration purposes. A quick overview of this task includes a Configuration Service that contains all of the methods used to push and pull data from a file. Once the data is pulled in, it is set to a class object that we will create. Let’s get started!

Project Layout

First, I am going to show you the project layout. This is going to be a rough draft as I might make changes to it in the future. Essentially, from the root I am going to have three folders: Configuration, Models and Services. The Configuration folder will contain files that relate to the application’s configuration and settings. The Models folder houses all of my data classes. The Services folder will hold all of my service classes. The main program file and other supporting files will remain in the root of the project.

project-structure

Data File

I am going to start with the data file which we will call settings.json. This will be a JSON file because it is very easy to read, and .NET has a really nice JSON library that makes it easy to work with. This file will reside in the Configuration folder. As of now, the file only has three properties: ApiKey, Country and Language. The ApiKey will contain the user’s API key from News Data. The Country and Language properties are arrays and they will contain country and language codes. The reason that they are arrays is because News Data allows up to five country and language codes per query. Each array may contain one value or five values. Let’s look at the file as a whole:

{
  "ApiKey": "YOUR_API_KEY_HERE",
  "Country": [
    "us"
  ],
  "Language": [
    "en"
  ]
}

Settings Class

Next up, is the Settings Class. This one is fairly straightforward, as it really just mimics the settings.json structure. This will be used to create a C# object of the data from the file after pulling it into the application. This file will be called Settings.cs. Below is the content of that file.

namespace NewsScout.Models
{
    public class Settings
    {
        public string? ApiKey { get; set; }
        public string[]? Country { get; set; }
        public string[]? Language { get; set; }
    }
}

Notice how the class property names are the same as the ones in the JSON file. This will make it easier for the application to deserialize the JSON content into the class object when we start working on the service.

Configuration Service

This is where the meat and potatoes are held. This file is named ConfigurationService.cs and will hold all of the methods that are used to create a data flow between the application and the JSON file. As of right now, I have two methods. One to pull the data into the application and another to save data from the application. We will look at this file in chunks so that it’s easier to understand and then at the end we will see the entire file. Let’s have a look at the load method, which is called LoadSettings():

public static Settings LoadSettings()
{
    string _fileName = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\Configuration\settings.json");

    try
    {
        string? _fileContents = File.ReadAllText(_fileName);
        return JsonSerializer.Deserialize<Settings>(_fileContents);
    }
    catch (Exception e)
    {
        WriteLine($"Error reading from {_fileName}. Message:\n\n{e.Message}");
        throw;
    }            
}

First, we set the file path to a variable and then read the file to another variable within a try block. We also try to deserialize the variable in that same try block. The reason that these two lines are in a try block is because if the application has any issues trying to perform these tasks it will throw an error. Most of the time these errors are very tough to read. But if you notice after the try block section there is a catch block section. Here we will take that error (if there is one) and present it in a more friendly way.

After we successfully deserialize the data, the method will return it as a Settings object (remember our Settings class?). To use this method in the main program you might run something like this:

Settings settingVariable = LoadData();

This ensures that when you run this method that return data is captured in a Settings object.

Next is the save method. It is named SaveSettings(). This is the same as before, but the method does not return anything (void), the data is serialized as JSON and then saved to the JSON file. Let’s take a look:

public static void SaveSettings(Settings? _settingsToSave)
{
    string _fileName = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\Configuration\settings.json");

    try
    {
        string _jsonContent = JsonSerializer.Serialize<Settings>(_settingsToSave, new JsonSerializerOptions() { WriteIndented = true });
        File.WriteAllText(_fileName, _jsonContent);
    }
    catch (Exception e)
    {
        WriteLine($"Here is the error message:\n\n{e.Message}");
        throw;
    }
}

One thing to note is the JsonSerializer line. This line has a little extra compared to its LoadSettings() counterpart. This one contains JsonSerializerOptions(). This allows you to add little options to the JSON as it is being serialized. To be honest, I did not realize that this was a thing. This came about because when you use JsonSerializer and write to a file, it will work but it writes it all in one line. I wasn’t a fan of this and after spending some time researching, I had found the serializer options. In this example, I use WriteIndented = true. This gives me the multiline, indented formatting that I like.

As promised, here is the entire ConfigurationService.cs file:

using NewsScout.Models;
using System.IO;
using System.Text.Json;

namespace NewsScout.Services
{
    public static class ConfigurationService
    {

        #region Methods

        // Load settings.json into Settings class
        public static Settings LoadSettings()
        {
            string _fileName = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\Configuration\settings.json");

            try
            {
                string? _fileContents = File.ReadAllText(_fileName);
                return JsonSerializer.Deserialize<Settings>(_fileContents);
            }
            catch (Exception e)
            {
                WriteLine($"Error reading from {_fileName}. Message:\n\n{e.Message}");
                throw;
            }            
        }

        // Save to settings.json from a Settings class object
        public static void SaveSettings(Settings? _settingsToSave)
        {
            string _fileName = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\Configuration\settings.json");

            try
            {
                string _jsonContent = JsonSerializer.Serialize<Settings>(_settingsToSave, new JsonSerializerOptions() { WriteIndented = true });
                File.WriteAllText(_fileName, _jsonContent);
            }
            catch (Exception e)
            {
                WriteLine($"Here is the error message:\n\n{e.Message}");
                throw;
            }
        }

        #endregion

    }
}

Testing

Lastly, I will run unit tests against all three of these subcomponents to make sure they work together as a whole. I am going to leave unit testing out of this post and probably do a general unit testing post. This completes the first little component of this entire project. If you missed out, here is Part I. Want to continue reading? Here is Part III. As always, if you have any questions or feedback, reach out to me on Twitter. See you next time.