Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Sep 6, 2023

Writing files with C#

Автор:
Kenji Elzerman
Источник:
Просмотров:
3504

Writing Files With C#

To write files with C#, you have to know how to do it. The basics aren’t rocket science. There are a few lines of code you need to know. But writing and reading files with C# is something every beginning C# developer should be able to do.

If you want to know how you can read the contents of a file using C#, you can read the article Reading Files With C#.

Things you should know.

It is pretty important you have some knowledge about C#. If you are a beginner, the chapters about encryption and decryption might be a bit too much.

I will be using C# and .NET 7 for this. My OS is Windows 11, which is a bit important since I don’t know how Linux works with special folders and the rights of creating and writing to files and folders.

The Goal

In this article, I want to show you:

  • How you can write lines of text to a file with the StreamWriter. I will also show you how you can append text to an existing file.
  • Show you what the so-called special folders are.
  • Explain and show you how you can encrypt and decrypt the data in your file.

I will start with a simple console application and work my way up from there. If you want to type along I suggest you create a console application first before you continue. I will be using .NET 7 and Visual Studio 2022 Community Preview.

To write files with C#, it really doesn’t matter what kind of file, you can use the StreamWriter. This class has several constructors with different parameters. The most used is the one that receives a path to a file. You can also set a Boolean if you want to append to the file (true) or empty the file before writing (false).

Default Encoding

If you want to set an encoding, the constructor is the place to set it. By default, the encoding is set to UTF8NoBOM. UTF-8 is a character encoding that represents each character in the Unicode standard using variable-length encoding The BOM stands for Byte Order Mark. The purpose of BOM is to indicate that the file has a UTF-8 encoding. The ‘No’ means it’s UTF-8 encoding without the byte order mark.

There is much more to UTF8NoBom, or its separate parts, but that’s not the goal of this article. If you change the encoding you need to keep this in mind. If you want to read from your file you need to set the encoding for specific encodings.

Filetypes

As I said before, it doesn’t really matter to what type of file you write. If you want to write to ‘my_awesome_file.klc’, go ahead. No problem there. But don’t use extensions that might exist. Don’t use .sln for example, or .doc. Use .txt or your own just to be safe.

Think of your own extensions and be original. With original I mean; don’t think about existing extensions. Use your initials or abbreviations. Kens Learning Curve could be klc, for example.

Write Lines

Alright, let’s write to files!

The StreamWriter inherits the TextWriter, which inherits IDisposable. That means we can use the ‘using’ to initialize and use the StreamWriter. The basic setup looks like this:

using (StreamWriter file = new("YourFileHere.klc"))
{

}

First I declare the variable file, which is an initialized StreamWriter. I send the path to the file with the constructor to the initialized StreamWriter. If you don’t set a path (I only have written the file name) the file will be stored in the bin/debug folder of your application. If you want your own path or choose a different folder, simply write out that path like this:

using (StreamWriter file = new(@"c:\path\to\YourFileHere.klc"))
{

}

Now we can write text to the file. There are two ways of writing, 4 if you count the async variants. Like the Console class, the StreamWriter has a Write and WriteLine. They do the exact same thing as the Console.Write and Console.WriteLine, except the StreamWriter ‘prints’ to a file instead of the console output.

using (StreamWriter file = new("YourFileHere.klc"))
{
    file.Write("Hello ");
    file.WriteLine(" world");

    file.WriteLine("Another line here");
}

This code results in the following text file:

YourFileHere.klc

Append Lines

If you restart the application again you will notice the text hasn’t changed. Well, it has changed, but you don’t notice it. By default, the StreamWriter empties the file and writes the text in the file again.

In some cases, you don’t want that. You might want to add lines of text. Instead of reading the whole file, placing it in a variable, adding the next text, and writing everything to the file, you can append text easily by making one change:

using (StreamWriter file = new("YourFileHere.klc", true))
{
    file.Write("Hello ");
    file.WriteLine(" world");

    file.WriteLine("Another line here");
}

The ‘true’ in the code example above means that the append is set to true. If you run the application now you will see that the text is being added to the file.

YourFileHere.klc

Using Folders

In the examples I used in this article, I have no path to the files I am writing to. It’s pretty easy to add a path. If I want to write a file to, let’s say, c:\my_folder\data.klc, I can simply put that path in the StreamWriter initialization.

Path To File

Just make sure you escape the ‘\’ characters or the verbatim string literal prefix ‘@’.

string fileName = @"d:\my_folder\data.klc";

using (StreamWriter file = new(fileName))
{    
    file.Write("hello ");
    file.WriteLine("world");

    file.WriteLine("another line here");
}

Make sure the folder exists, because if it doesn’t you will get an exception and the application stops working.

Program.cs

The best way to fix this is to check if the folder exists, if it doesn’t you create it. But we have the whole path in one string. We need to separate the segments (folder and file) and combine them when we want to write the file.

The class Path has a create method for combining folders and files. You don’t have to worry about slashes, escaping characters, and spaces in your path. It will take as many segments as you need and it will create one string with all the segments.

Take a look at the code below:

string folder = @"d:\my_folder\";
string filename = "data.klc";
string path = Path.Combine(folder, filename);

if(!Directory.Exists(folder))
    Directory.CreateDirectory(folder);

using (StreamWriter file = new(path))
{    
    file.Write("hello ");
    file.WriteLine("world");

    file.WriteLine("another line here");
}

I separated the folder and filename into different variables. Then I combine them in a whole path on line 3 with the Path.Combine method.

Next, I check if the folder exists. If not, I will create the folder and move on. If the folder does exist it will ignore the CreateDirectory and write the text to the file.

Special Folders

As a Windows user, I have some special folders. Not sure if this works on Linux as well, because I don’t use Linux (yet).

These special Windows folders are for example My Documents, My Pictures, Program Files, and so on. The type of folders you won’t use that often.

There is a whole list of special folders, but these are the most common ones:

Special Folders

If you want to write files with C# in a special folder you can use the enumerable System.Environment.SpecialFolder. Let’s say we want to write a file to the My Documents folder.

As you might know, an enumerable is not a string and you can’t use the System.Environment.SpecialFolder.MyDocuments in the Path.Combine. You need the Environment.GetFolderPath() which gets an item from the Environment.SpecialFolder. In the end, the code will look like this:

string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "YourFileHere.klc");

using (StreamWriter file = new(path))
{    
    file.Write("hello ");
    file.WriteLine("world");

    file.WriteLine("another line here");
}

Keep The Data Safe

If you have an application that writes information to a file and you don’t want everyone to be able to read it, you can consider encrypting the data of that file. I am not going to explain how the whole encryption in C# works, that’s a whole other level. I am just showing you how you can do it with just the right information. After you encrypted your data you might want to decrypt it so you can read it again.

Let’s look at how you can write files with C# and encrypt the information.

Encrypting

In C# there is a class called System.Security.Cryptography, which holds a lot of methods that help you encrypt data. The idea is to encrypt the string you want to write to a file before you write it into said file.

Here is the example code. It’s the same as the previous one, but now with encryption.

string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "YourFileHere.klc");

byte[] key;
byte[] iv;

using (StreamWriter file = new(path))
{
    string toWrite = "Hello world! This is an encrypted text!";

    file.Write(Encrypt(toWrite));
}

string Encrypt(string plainText)
{
    using Aes aesAlg = Aes.Create();

    aesAlg.GenerateKey();
    aesAlg.GenerateIV();

    key = aesAlg.Key;
    iv = aesAlg.IV;

    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

    using MemoryStream msEncrypt = new();
    using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write);
    using (StreamWriter swEncrypt = new(csEncrypt))
    {
        swEncrypt.Write(plainText);
    }
    byte[] encryptedBytes = msEncrypt.ToArray();
    return Convert.ToBase64String(encryptedBytes);
}

I have added two-byte arrays: key and iv. The key is literally the key to the encryption. With this key, I can unlock the encrypted content and make it readable again.

Iv stands for Initialization Vector and it is a random value. it is the starting point for the encryption process. It ensures that if you encrypt the same data multiple times, the result will be different.

We need both the key and the IV to encrypt and decrypt the text, so it’s a good idea to save them if you want to decrypt the data in the future.

The method Encrypt(string plainText) is all about encrypting. AES stands for Advanced Encryption Standard and is a commonly used symmetric encryption algorithm. It divides the data into blocks and then applies a series of transformations for each block (substitution, permutation, and some mixing operations).

First, initialize the Aes class. Then I let AES generate a key and IV for me, which I both store in the key and iv variables. You can generate your own keys, but it takes more code and I want to keep it simple.

Next, I create an encryptor with the aesAlg.CreateEncryptor. This is an object that can perform the encryption of the data, using the key and the iv.

The encrypted data is written into the memory of the machine, with a StreamWriter. The StreamWriter needs the CryptoStream, which uses a MemoryStream, the encryptor, and the stream mode. The stream mode is write since we want to write the encrypted data to the memory.

The encrypted data is now in the memory stream. To extract that we place it in a byte array, convert that array to a base 64 string, and return it.

That’s it! Now we can write this to a file. The content of the file looks a bit like this:

Encrypting

Decrypting

If you want to make your encrypted data readable again you need to decrypt it. You can’t decrypt data if you don’t have the key to unlock it.

Luckily, the code of decrypting is a bit easier than encrypting:

string Decrypt(string encryptedData)
{
    byte[] encryptedBytes = Convert.FromBase64String(encryptedData);

    using Aes aesAlg = Aes.Create();
    aesAlg.Key = key;
    aesAlg.IV = iv;

    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

    using MemoryStream msDecrypt = new(encryptedBytes);
    using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
    using StreamReader srDecrypt = new(csDecrypt);
    return srDecrypt.ReadToEnd();
}

The first thing we need to do is convert the data from the file — a string — to a byte array. After that, we create a new Aes object and tell AES what the key and IV are. Instead of an encryptor, we create a decryptor, which receives the key and IV.

Next, we create a new MemoryString, which gets the encrypted bytes. Then a new CryptoStream, which gets the initialized MemorgStream, the decryptor, and a steam mode of Read. And last, we create a StreamReader that gets the CryptoStream.

The StreamReader holds all the information and can read the readable data and send it back. With the ReadToEnd() you retrieve all decrypted data.

The whole Program.cs of my console application look like this:

using System.Security.Cryptography;

string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "YourFileHere.klc");

byte[] key;
byte[] iv;

using (StreamWriter file = new(path))
{
    string toWrite = "Hello world! This is an encrypted text!";

    file.Write(Encrypt(toWrite));
}

using (StreamReader reader = new(path))
{
    string content = reader.ReadToEnd();

    string readableContent = Decrypt(content);

    Console.WriteLine(readableContent);
}

string Encrypt(string plainText)
{
    using Aes aesAlg = Aes.Create();
    aesAlg.GenerateKey();
    aesAlg.GenerateIV();

    key = aesAlg.Key;
    iv = aesAlg.IV;

    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

    using MemoryStream msEncrypt = new();
    using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write);
    using (StreamWriter swEncrypt = new(csEncrypt))
    {
        swEncrypt.Write(plainText);
    }
    byte[] encryptedBytes = msEncrypt.ToArray();
    return Convert.ToBase64String(encryptedBytes);
}

string Decrypt(string encryptedData)
{
    byte[] encryptedBytes = Convert.FromBase64String(encryptedData);

    using Aes aesAlg = Aes.Create();
    aesAlg.Key = key;
    aesAlg.IV = iv;

    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

    using MemoryStream msDecrypt = new(encryptedBytes);
    using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
    using StreamReader srDecrypt = new(csDecrypt);
    return srDecrypt.ReadToEnd();
}

And if I run this a YourFileHere.klc file is being created in My Documents. I add a StreamReader to read the contents of the file. The contents in the file are encrypted, but the console application shows the correct, readable information:

Decrypting

Conclusion

Well, that was pretty cool, right? In this article, you have seen how you can easily write content from C# into a file. There are many reasons why you should write information to files. But always keep in mind the safety of your users: Never write sensitive information in a readable fashion to files.

Of course, there is much more to encryption, but this was an extra. But I do hope you got the idea. There are other ways of encrypting data, but AES is the most common and used in C# and .NET.

Похожее
Dec 25, 2022
Author: Sannan Malik
This article will look at the differences between static and readonly C# fields. Basically, a static readonly field can never be replaced by an instance of a reference type. In contrast, a const constant object cannot be replaced by a...
Nov 27, 2023
Author: Juldhais Hengkyawan
Use the Bogus library to generate and insert 1 million dummy product data into the SQL Server database We need to create 1 million dummy product data into the SQL Server database, which can be used for development or performance...
Dec 1, 2023
Author: Shachar Shamir
  While sometimes there’s nothing to do but crack open that energy drink and grind out an all-nighter, coders tend to work smarter, not harder by finding ways to increase their productivity without sacrificing work quality. No matter what specific...
Oct 17
Author: walter Torricos
Intro What is the Result pattern? Basically it is a great way to write error-tolerant code that can be composed. Do you feel that phrase sounds familiar? If you like F# you are right, I’ve taken it from the Results...
Написать сообщение
Тип
Почта
Имя
*Сообщение