Let's start by discussing the logic behind a Virtual Storage Space. This is a single virtual representation of several physical spaces on your hard drive (or hard drives). A storage space will be seen as a single area where a specific group of eBooks are stored. I use the term stored loosely because the storage space doesn't exist. It represents more of a grouping than a physical space on the hard drive:
- To start creating Virtual Storage Spaces, add a new class called StorageSpace to the eBookManager.Engine project. Open the StorageSpace.cs file and add the following code to it:
using System;
using System.Collections.Generic;
namespace eBookManager.Engine
{
[Serializable]
public class StorageSpace
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Document> BookList { get; set; }
}
}
Note that you need to include the System.Collections.Generic namespace here, because the StorageSpace class contains a property called BookList of type List<Document> that will contain all the books in that particular storage space.
Now we need to focus our attention on the ExtensionMethods class in the eBookManager.Helper project. This will be a static class, because extension methods need to be static in nature in order to act on the various objects defined by the extension methods.
-
Add a new class to the eBookManager.Helper project and modify the ExtensionMethods class as follows:
public static class ExtensionMethods
{
}
Let's add the first extension method to the class called ToInt(). What this extension method does is take a string value and try to parse it to an integer value. I am too lazy to type Convert.ToInt32(stringVariable) whenever I need to convert a string to an integer. It is for this reason that I use an extension method.
- Add the following static method to the ExtensionMethods class:
public static int ToInt(this string value, int defaultInteger = 0)
{
try
{
if (int.TryParse(value, out int validInteger))
// Out variables
return validInteger;
else
return defaultInteger;
}
catch
{
return defaultInteger;
}
}
The ToInt() extension method acts only on string. This is defined by the code this string value in the method signature where value is the variable name that will contain the string you are trying to convert to an integer. It also has a default parameter called defaultInteger, which is set to 0. Unless the developer calling the extension method wants to return a default integer value of 0, they can pass a different integer to this extension method (-1, for example).
It is also here that we find our first feature of C# 7. The improvement to out variables. In previous iterations of C#, we had to do the following with out variables:
int validInteger;
if (int.TryParse(value, out validInteger))
{
}
There was this predeclared integer variable hanging around and it gets its value if the string value parsed to an integer. C# 7 simplifies the code a lot more:
if (int.TryParse(value, out int validInteger))
C# 7 allows developers to declare an out variable right there where it is passed as an out argument. Moving along to the other methods of the ExtensionMethods class, these methods are used to provide the following logic:
-
Read and write to the data source
-
Check whether a storage space exists
-
Convert bytes to megabytes
-
Convert a string to an integer (as discussed previously)
The ToMegabytes method is quite easy. Not having to write this calculation all over the place, defining it inside an extension method makes sense:
public static double ToMegabytes(this long bytes)
{
return (bytes > 0) ? (bytes / 1024f) / 1024f : bytes;
}
We also need a way to check if a particular storage space already exists.
Be sure to add a project reference to eBookManager.Engine from the eBookManager.Helper project.
What this extension method also does is to return the next storage space ID to the calling code. If the storage space does not exist, the returned ID will be the next ID that can be used when creating a new storage space:
public static bool StorageSpaceExists(this List<StorageSpace> space, string nameValueToCheck, out int storageSpaceId)
{
bool exists = false;
storageSpaceId = 0;
if (space.Count() != 0)
{
int count = (from r in space
where r.Name.Equals(nameValueToCheck)
select r).Count();
if (count > 0)
exists = true;
storageSpaceId = (from r in space
select r.ID).Max() + 1;
}
return exists;
}
We also need to create a method that will write the data we have to a file after converting it to JSON:
public static void WriteToDataStore(this List<StorageSpace> value, string storagePath, bool appendToExistingFile = false)
{
JsonSerializer json = new JsonSerializer();
json.Formatting = Formatting.Indented;
using (StreamWriter sw = new StreamWriter(storagePath,
appendToExistingFile))
{
using (JsonWriter writer = new JsonTextWriter(sw))
{
json.Serialize(writer, value);
}
}
}
This method is rather self-explanatory. It acts on a List<StorageSpace> object and will create the JSON data, overwriting a file defined in the storagePath variable.
Lastly, we need to be able to read the data back again into a List<StorageSpace> object and return that to the calling code:
public static List<StorageSpace> ReadFromDataStore(this List<StorageSpace> value, string storagePath)
{
JsonSerializer json = new JsonSerializer();
if (!File.Exists(storagePath))
{
var newFile = File.Create(storagePath);
newFile.Close();
}
using (StreamReader sr = new StreamReader(storagePath))
{
using (JsonReader reader = new JsonTextReader(sr))
{
var retVal =
json.Deserialize<List<StorageSpace>>(reader);
if (retVal is null)
retVal = new List<StorageSpace>();
return retVal;
}
}
}
The method will return an empty List<StorageSpace> object and nothing is contained in the file. The ExtensionMethods class can contain many more extension methods that you might use often. It is a great way to separate often-used code.