rskibbe.Ini – Parsing, reading & saving INI files in .NET – but easy!
Inhaltsverzeichnis
- 1 What is rskibbe.Ini?
- 2 The easiest INI example – (possibly) ever
- 3 Planned features
- 4 ++ Update 03.10.23 – v1.6.0 ++
- 5 ++ Update 09.09.23 ++
- 6 ++ Update 29.03.23 ++
- 7 ++ Update 08.03.23 ++
- 8 Quick example
- 9 Full example – Loading / Saving a Database config
- 10 Update details 29.03.23
- 11 Security aspects
- 12 Reporting problems
- 13 Downloads
What is rskibbe.Ini?
rskibbe.Ini is a simple package to help you working with the INI format: Parsing from/to string and creating and saving ini files – but easy! Setting and reading common values like strings, integers and more can be done by using corresponding functions like „GetString / SetString“ and so on. If you want to store common things like FTP, database or mail credentials, you can prepare those settings with one single line. You will also find an INI, Section and Entry specific cypher functionality, to protect your data a bit better.
The easiest INI example – (possibly) ever
If you want the shortest form like possible, go for the static (shared in VB.NET) „Current“ property of the IniFile class. This property provides a global singleton to use, where you need to pretty much do nothing. Now, the current configuration inside the INI will be saved, when the Form1 is being closed. In the meantime (between the start and end of your program), use the „SetString“, etc. functions from below to adjust your settings from user input inside your forms.
Setup
Here’s an example of creating a ready to use ini file instance on startup. Keep in mind, that this won’t actually create the file on the harddrive, as we used the „non strict: false“ argument on the „LoadAsync“ method. This means: Load the contents of this file if available, if it isn’t available, just give me a „ready to use“ object instance.
We then set some values using a few of the various „Set*“ methods by pulling some data out of the UI inside of the „btnConfirm_Click“ handler method.
When the form gets closed, we will save / persist the current sections and their values onto the harddrive overriding the previous config file.
// needed import using rskibbe.Ini.Models; private async void Form1_Load(object sender, EventArgs e) { await IniFile.Current .LoadAsync(); } private void btnConfirm_Click(object sender, EventArgs e) { IniFile .Current .SetString("<sectionname>", "Key1", tbSomeTextbox.Text) .SetString("<sectionname>", "Key2", tbSomeOtherTextbox.Text) .SetInt("<sectionname>", "Key3", Convert.ToInt32(tbSomePortTextbox.Text)); } private async void Form1_FormClosing(object? sender, FormClosingEventArgs e) { await IniFile.Current .SaveAsync(); }
' needed import Imports rskibbe.Ini.Models Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Await IniFile.Current _ .LoadAsync() End Sub Private Sub btnConfirm_Click(sender As Object, e As EventArgs) Handles btnConfirm.Click IniFile _ .Current _ .SetString("<sectionname>", "Key1", tbSomeTextbox.Text) _ .SetString("<sectionname>", "Key2", tbSomeOtherTextbox.Text) _ .SetInt("<sectionname>", "Key3", Convert.ToInt32(tbSomePortTextbox.Text)) End Sub Private Async Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing Await IniFile.Current _ .SaveAsync() End Sub
Storing & persisting values
While the code from above is just the basic „shell“ around your INI things, the following actually stores values inside your ini class instance. Imagine your user entering some values into your textboxes on your form (or WPF Window..). For sure this would also work for a simple console like app.. You could then react to like a button click and put the values into your INI. The next time, the window is closed, the settings will get serialised to your haddrive / specific settings path. If not specified otherwise, it’s „settings.ini“ by default.
private void Button1_Click(object sender, EventArgs e) { // will be saved, the next time, the window closes.. IniFile.Current.SetString("Ftp", "User", TextBox1.Text); // IniFile.Current.SetInt(...); // more.. // if it's necessary to be saved immediately, use one of the Save methods // don't forget to mark it as async if you use the async / await approach // await IniFile.Current.SaveAsync(); }
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' will be saved, the next time, the window closes.. IniFile.Current.SetString("Ftp", "User", TextBox1.Text) ' IniFile.Current.SetInt(...) ' more.. ' if it's necessary to be saved immediately, use one of the Save methods ' don't forget to mark it as async if you use the async / await approach ' Await IniFile.Current.SaveAsync() End Sub
Planned features
As you can probably tell, I’m using this library myself a lot, I mean, why wouldn’t I create it if I can’t find it useful myself, right!? While using there will sometimes sometimes be situations where you (the readers) or I am like: Hmm, why can’t I just do it like this / this is missing. Common left overs of wishes will be listed here and may be integrated in future versions of rskibbe.Ini. I simply want to make this helper package some sort of „definitive edition“ for INI files.
Only saving the config if needed
When thinking about designing intelligent software, it doesn’t feel good when your program accesses your harddrive everytime when exiting your app. I should just do, if it’s actually needed – the config has been changed. We can’t impede this while booting our app, as we need to load the config, but probably not on save / exit.
Custom parsers
Imagine someone asking you „Are you okay?“ and you answer with „yes. Now try to ask a machine, it will probably answer with „1“ meaning „yes“ / „true“ as well – ok jokes aside. As you can see there can be different interpretations of a „true“. Some developers will use like „1“ for their boolean setting, and some will use „si“ / „yes“. I’m thinking of creating the possibility to provide your own parser for methods like „GetBoolean“. Let’s see what the future holds!
++ Update 03.10.23 – v1.6.0 ++
Important: Please keep common security aspects in mind when using (for example) the „WithLoginSection“ helper method introduced in this version.
- Added login config helper „WithLoginSection“
- Bugfix considering namespacing
- Bugfix considering line terminator
- Rather unimportant inheritance things
- Improved documentation
- Small changes on methods
- Added sync method alternatives
- Line terminator default is now platform specific (Windows / Unix)
++ Update 09.09.23 ++
CAUTION: The important stuff first – until now there was an automatic setting of a Cypher when you used the (for example) „WithDatabaseSettings“ helper method. To improve by being on a common base, I removed that and made specifying this optional. Furthermore I increased the compatibility for this library by lowering down it’s target framework to „.net standard 2.0“.
++ Update 29.03.23 ++
The library has been updated to make your INI-life even easier – short version:
- store common settings with ease
- avoid clear text passwords or similar data with using ICypher implementations
- parser ignores comments (lines starting with „;“ or „#“) as you are used to within the INI space
More to read at „29.03.2023 update details„..
++ Update 08.03.23 ++
Added more documentation and examples, as well as updating the library to support more convenient functions like „GetString“, etc. Take a look at step 4, to see how it works. I’ve also added some Visual Basic (VB.NET) Code. This will greatly improve using the library, making your configuration work even easier.
Quick example
Without talking to much, I’ll give you this quick example, just go into the specific sections of this article, to get more details. There you can find more information about specific steps and how to reproduce them.
Step 1 – Install the package
Open the NuGet Package Manager and execute the following commands (or install it by GUI). This command will install the package to enable you using its code.
Install-Package rskibbe.Ini Install-Package rskibbe.Encryption
As you can see you will need to install „rskibbe.Encryption“ as well. It’s just a very small package representing a common denominator for some basic things. If you want to know more about it, feel free to visit the „rskibbe.Encryption“ page where I explained everything. To be complete, the „rskibbe.Encryption.Base64“ package is also very important – but you don’t need it for the basic usage.
Step 2 – Creating an INI based instance
Next, you can create a simple INI by providing some information as shown below. Keep in mind, that „INI“ is roughly / only a format which I personally don’t compulsorily associate with something like a „file“. For me, it’s primarily a format, which represents a „parent“, with a set of child „sections“. In the end, the sections can contain multiple entries, which are basically key & value pairs – that’s it.
With this basic conception in mind, it doesn’t obviously matter, where the string comes from. If the string is in the right format, everything is good.
Loading from file
As the most common usecase will be the loading from files, this will be the first example to be shown. Just specify the target file to be loaded and you are good to go. If you omit the filepath, it will take the default path – being a file called „settings.ini“ in the current folder of the executable.
// without specifying a filepath = defaulting to "settings.ini" var iniFile = await IniFile.LoadAsync(); // with specific file var iniFile = await IniFile.LoadAsync(".\\config.ini");
' without specifying a filepath = defaulting to "settings.ini" Dim iniFile = Await IniFile.LoadAsync() ' with specific file Dim iniFile = Await IniFile.LoadAsync(".\\config.ini")
Parsing / loading from string
Next to the typical file based usecase, you can also parse the ini from a simple string. Usual ini files (contents / strings) are separated through some kind of delimiter. Mostly, this is some kind of newline, you can specify the parameter inside the „FromString“ method. If omitted, the default will be „carriage return, linefeed“.
// omitting the delimiter var ini = rskibbe.Ini.Models.Ini.FromString("[FtpSettings]user=someuser\r\npassword=mypw"); // manually specifying the delimiter var ini = rskibbe.Ini.Models.Ini.FromString("[FtpSettings]user=someuser\r\npassword=mypw", "\r\n");
' omitting the delimiter Dim ini = rskibbe.Ini.Models.Ini.FromString("[FtpSettings]user=someuser\r\npassword=mypw") ' manually specifying the delimiter Dim ini = rskibbe.Ini.Models.Ini.FromString("[FtpSettings]user=someuser\r\npassword=mypw", vbCrLf)
Creating the „typical“ way
One of the most common ways of creating an ini based „thing“, is „the usual“ way, which is known from a specific API. The lib supports this style of creation as well. Take a look at the following example:
// or create an IniFile instead.. var ini = new rskibbe.Ini.Models.Ini(); ini.SetString("Db", "Host", "theValue"); ini.SetInt("Db", "Port", 3306); // more..
' or create an IniFile instead.. Dim ini = new rskibbe.Ini.Models.Ini() ini.SetString("Db", "Host", "theValue") ini.SetInt("Db", "Port", 3306) ' more..
Creating an ini via fluent api
If you want to create your ini based instance pretty easily, you can use the fluent API to do so.
// manual step by step creation var ini = rskibbe.Ini.Models.Ini // using the "Factory Property" .New // create the first section and.. .AddSection("NetworkSettings", section => { // use an anonymous action with Section argument to register entries section // register the first entry .AddEntry("host", "127.0.0.1") // ... .AddEntry("user", "networkuser"); }) // being able to chain and keeping a nice fluent api .AddSection("FtpSettings", section => { section .AddEntry("user", "ftpuser") .AddEntry("password", "ftppw"); });
' manual step by step creation Dim ini As IIni = rskibbe.Ini.Models.Ini _ ' using the "Factory Property" .New _ ' create the first section and.. .AddSection("NetworkSettings", Sub(section) ' use an anonymous action with Section argument to register entries section _ ' register the first entry .AddEntry("host", "127.0.0.1") _ ' ... .AddEntry("user", "networkuser") End Sub) ' being able to chain and keeping a nice fluent api .AddSection("FtpSettings", Sub(section) section _ .AddEntry("user", "ftpuser") _ .AddEntry("password", "ftppw") End Sub)
Step 3 – Transform from Ini to an IniFile instance
You can for example transform the above into an IniFile instance and save it to disc, with the following code. This could be helpful, if you like receive a string from a foreign source, but want to create a file based on it.
Transforming from Ini instance
// transforming an ini instance to an IniFile instance var iniFile = ini.ToFile(".\\settings.ini"); // now save that file // don't forget ConfigureAwait(false), if it's applicable await iniFile.SaveAsync();
' transforming an ini instance to an IniFile instance (IInifile interface..) Dim iniFile As IIniFile = ini.ToFile(".\\settings.ini") ' now save that file ' don't forget ConfigureAwait(False), if it's applicable Await iniFile.SaveAsync()
Directly creating an IniFile instance
var iniFile = IniFile .New .AddSection("NetworkSettings", section => { // use an anonymous action with Section argument to register entries section // register the first entry .AddEntry("host", "127.0.0.1") // ... .AddEntry("user", "networkuser"); }) .AddSection("FtpSettings", section => { section .AddEntry("user", "ftpuser") .AddEntry("password", "ftppw"); });
Dim iniFile As IniFile = IniFile _ .New _ .AddSection("NetworkSettings", Sub(section) ' use an anonymous action with Section argument to register entries section _ ' register the first entry .AddEntry("host", "127.0.0.1") _ ' ... .AddEntry("user", "networkuser") End Sub) _ .AddSection("FtpSettings", Sub(section) section _ .AddEntry("user", "ftpuser") _ .AddEntry("password", "ftppw") End Sub);
Step 4 – Getting data from the INI File
In the next steps, I will show you some basic examples about getting sections, entries and values out of the ini. Keep in mind, that you will need an existing Ini or IniFile instance.
Getting a whole section
If you know, that you will have to access multiple entries / values after another anyways, you should pull the complete section. This will avoid additional checks, even though those checks are minimal and mostly dictionary based inside = O(1) operations.
// checking if a section exists bool ftpSettingsSectionExists = ini.SectionExists("FtpSettings"); // via indexer - throws SectionNotFoundException if not existent ISection ftpSettingsSection = ini["FtpSettings"]; // via method - throws SectionNotFoundException if not existent ISection ftpSettingsSection = ini.GetSection("FtpSettings"); // via safe method - being able to check on the fly if (ini.TryGetSection("FtpSettings", out ISection ftpSettingsSection)) { // do something with the "ftpSettingsSection" variable }
' checking if a section exists Dim ftpSettingsSectionExists As Boolean = ini.SectionExists("FtpSettings") ' via indexer - throws SectionNotFoundException if not existent Dim ftpSettingsSection As ISection = ini("FtpSettings") ' via method - throws SectionNotFoundException if not existent Dim ftpSettingsSection As ISection = ini.GetSection("FtpSettings") ' via safe method - being able to check on the fly Dim section As ISection = Nothing If ini.TryGetSection("FtpSettings", ftpSettingsSection) Then ' do something with the "ftpSettingsSection" variable End If
Getting entries out of a section
After you’ve successfully pulled out the corresponding section of the Ini / IniFile, you can now get the desired entries. You can also find an easier method further down. Entries are basically Key-Value-Pairs inside of the section.
// checking if an entry exists bool ftpUserEntryExists = ftpSettingsSection.EntryExists("User"); // via indexer - throws EntryNotFoundException if not existent IEntry ftpUserEntry = ftpSettingsSection["User"]; // via method - throws EntryNotFoundException if not existent IEntry ftpUserEntry = ftpSettingsSection.GetEntry("User"); // via safe method - being able to check on the fly if (ftpSettingsSection.TryGetEntry("User", out IEntry ftpUserEntry )) { // do something with the "ftpUserEntry" variable }
' checking if an entry exists Dim ftpUserEntryExists As Boolean = ftpSettingsSection.EntryExists("User") ' via indexer - throws EntryNotFoundException if not existent Dim ftpUserEntry As IEntry = ftpSettingsSection("User") ' via method - throws EntryNotFoundException if not existent Dim ftpUserEntry As IEntry = ftpSettingsSection.GetEntry("User") ' via safe method - being able to check on the fly Dim ftpUserEntry As IEntry = Nothing If ftpSettingsSection.TryGetEntry("User", ftpUserEntry) Then ' do something with the "ftpUserEntry" variable End If
Getting values out of the ini, sections & entries
After learning about the Ini, IniFile and Section, we can now take a look about fetching values from those. Basically, there are two ways: Doing it „manually“, or using the quicker helper functions. Peek at the following examples:
// more easy / convenient - won't throw, if not existent, see "defaultValue" param string ftpUser = ini.GetString("FtpSettings", "User"); int ftpPort = ini.GetInt("FtpSettings", "Port"); // same as above, but with custom default value string ftpUser = ini.GetString("FtpSettings", "User", "defaultUser"); int ftpPort = ini.GetInt("FtpSettings", "Port", 21); // if you already have a section instance - throws EntryNotFoundException if not existent & is always string string ftpUser = ftpSettingsSection["User"]; string ftpPort = ftpSettingsSection["Port"]; // if you already have a section - more convenient way string ftpUser = ftpSettingsSection.GetString("User"); int ftpPort = ftpSettingsSection.GetInt("Port"); // in one line - could throw an exception if not existent & is always string string ftpPassword = ini["FtpSettings"]["User"];
' more easy / convenient - won't throw, if not existent, see "defaultValue" param Dim ftpUser As String = ini.GetString("FtpSettings", "User") Dim ftpPort As Integer = ini.GetInt("FtpSettings", "Port") ' same as above, but with custom default value Dim ftpUser As String = ini.GetString("FtpSettings", "User", "defaultUser") Dim ftpPort As Integer = ini.GetInt("FtpSettings", "Port", 21) ' if you already have a section instance - throws EntryNotFoundException if not existent & is always string Dim ftpUser As String = ftpSettingsSection("User") Dim ftpPort As String = ftpSettingsSection("Port") ' if you already have a section - more convenient way Dim ftpUser As String = ftpSettingsSection.GetString("User") Dim ftpPort As Integer = ftpSettingsSection.GetInt("Port") ' in one line - could throw an exception if not existent & is always string Dim ftpPassword As String = ini("FtpSettings")("User")
If you want, you can take a look at the complete example below, or even download it. This way, you will get started immediately.
Full example – Loading / Saving a Database config
To get started quickly, take a look at the following, complete example. I will show you, how you can like load a database configuration from file and save it back after configuring it. For sure, this style also applies for most other applications like WPF and Console Apps.
Create an App of your desire
For our testcase, just create a simple winforms app like the following. It will consist of multiple label and input pairs, just textboxes and one input for the port. The app itself will be a demo, for like storing needed database connection credentials.
Caution: Security hints
Keep in mind, that this will just be a little example, showing / explaining the functionality of the library. In a real life scenario, you shouldn’t store passwords in clear text, at least encode them. You should consider using an API layer for the database access as well, so nobody has direct access to your database. For some local systems, the above approach could be sufficient – but keep all those things in mind.
Loading settings
Usually, we should have settings to load at first (thus, having saved some beforehand), but this is no problem, as the library can handle default values. So let’s continue with the most important part: Loading settings from the IniFile. At this point, I think you’ve already created the little sample Form from the image above.
In the first step, we will create some constant to actually keep the same unchangeable name for our application lifetime. In our case, I will do this inside the main form (called Form1 here). Otherwise, you could do this inside your DI container or similar bootstrapp situations.
Create these two „fields“ inside the form and prepare the „Form1_Load“ method like this:
public const string ConfigFilePath = @".\config.ini"; public IIniFile Config { get; set; } public Form1() { InitializeComponent(); Load += Form1_Load; } private async void Form1_Load(object sender, EventArgs e) { await LoadSettingsAsync(); } private async Task LoadSettingsAsync() { try { if (IO.File.Exists(ConfigFilePath)) { Config = await IniFile.LoadAsync(ConfigFilePath); } else { await IO.File.WriteAllTextAsync(ConfigFilePath, string.Empty); Config = new IniFile(ConfigFilePath); } } catch (Exception ex) { MessageBox.Show($"Error loading config: `{ex.Message}`"); } tbDbHost.Text = Config.GetString("Db", "Host"); tbDbDb.Text = Config.GetString("Db", "Name"); tbDbUser.Text = Config.GetString("Db", "User"); tbDbPassword.Text = Config.GetString("Db", "Password"); nudDbPort.Value = Config.GetDecimal("Db", "Port", 3306); }
Public Const ConfigFilePath As String = ".\config.ini" Public Property Config As IIniFile Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Await LoadSettingsAsync() End Sub Private Async Function LoadSettingsAsync() As Task Try If IO.File.Exists(ConfigFilePath) Then Config = Await IniFile.LoadAsync(ConfigFilePath) Else Await IO.File.WriteAllTextAsync(ConfigFilePath, String.Empty) Config = New IniFile(ConfigFilePath) End If Catch ex As Exception MessageBox.Show($"Error loading config: `{ex.Message}`") End Try tbDbHost.Text = Config.GetString("Db", "Host") tbDbDb.Text = Config.GetString("Db", "Name") tbDbUser.Text = Config.GetString("Db", "User") tbDbPassword.Text = Config.GetString("Db", "Password") nudDbPort.Value = Config.GetDecimal("Db", "Port", 3306) End Function
Saving / setting values
In the next step, we will handle the actual saving / setting of our values. This will mostly happen at some important point in the application lifecycle, like the closing of the form, etc. This will avoid spamming operations on the harddrive. For now, we will trigger the saving process when the corresponding button is clicked.
Inside of that button, we will check, if all needed data is provided by the user, otherwise, we will show a little alert. If everything is filled out, we will trigger the actual saving process itself.
public Form1() { InitializeComponent(); // attach the click handler here, or inside of the designer btnSave.Click += btnSave_Click; } private async void btnSave_Click(object sender, EventArgs e) { if (!CheckData()) return; await SaveDataAsync(); } private bool CheckData() { if (tbDbHost.Text == "") { MessageBox.Show("Please provide the DB host"); return false; } if (tbDbDb.Text == "") { MessageBox.Show("Please provide the DB name"); return false; } if (tbDbUser.Text == "") { MessageBox.Show("Please provide the DB user"); return false; } if (tbDbPassword.Text == "") { MessageBox.Show("Please provide the DB password"); return false; } return true; } private async Task SaveDataAsync() { Config.SetString("Db", "Host", tbDbHost.Text); Config.SetString("Db", "Name", tbDbDb.Text); Config.SetString("Db", "User", tbDbUser.Text); // in a real world scenario you should at least // encode / decode the password // don't store it in clear form Config.SetString("Db", "Password", tbDbPassword.Text); Config.SetInt("Db", "Port", Convert.ToInt32(nudDbPort.Value)); var nl = Environment.NewLine; try { await Config.SaveAsync() ConfigureAwait(false); MessageBox.Show($"Config file `{Config.FilePath}` has been saved"); } catch (Exception ex) { MessageBox.Show($"Error saving config file `{Config.FilePath}`:{nl}{nl}{ex.Message}"); } }
Private Async Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSaved.Click If Not CheckData() Then Return End If Await SaveDataAsync() End Sub Private Function CheckData() As Boolean If tbDbHost.Text = "" Then MessageBox.Show("Please provide the DB host") Return False End If If tbDbDb.Text = "" Then MessageBox.Show("Please provide the DB name") Return False End If If tbDbUser.Text = "" Then MessageBox.Show("Please provide the DB user") Return False End If If tbDbPassword.Text = "" Then MessageBox.Show("Please provide the DB password") Return False End If Return True End Function Private Async Function SaveDataAsync() As Task Config.SetString("Db", "Host", tbDbHost.Text) Config.SetString("Db", "Name", tbDbDb.Text) Config.SetString("Db", "User", tbDbUser.Text) ' in a real world scenario you should at least ' encode / decode the password ' don't store it in clear form Config.SetString("Db", "Password", tbDbPassword.Text) Config.SetInt("Db", "Port", Convert.ToInt32(nudDbPort.Value)) Dim nl = Environment.NewLine Try Await Config.SaveAsync() _ .ConfigureAwait(False) MessageBox.Show($"Config file `{Config.FilePath}` has been saved") Catch ex As Exception MessageBox.Show($"Error saving config file `{Config.FilePath}`:{nl}{nl}{ex.Message}") End Try End Function
Update details 29.03.23
You can now „use“ comments inside your ini file, don’t even know why I forgot about them. Maybe because I personally never needed them.. The ini parsing mechanism will therefore ignore existing comments. So far, I didn’t implement keeping them, meh. I think, I will do this like in the next update. This means, the comments will get deleted when saving the INI file again.
I had to create 3 projects recently for my customers. One thing in common was, that I always needed some sort of „common configurations“. 2 projects were direct connections to databased in a local network, the other one was a FTP config. As we developers love the „DRY“ principle, I seriously don’t want to repeat myself everytime. Therefore I added helper methods, helping you to create those simple specific sections with just a simple line. Take a look at „common configuration sections“, to get to know more about this.
The next step after those common configuration sections was like: „Hmm, storing those credentials in clear text isn’t nice..“. So far, I handled this kind of obfuscation / encryption manually, but I don’t want to do this manually anymore. As a result, I’ve added a new interface „ICypher“ and a basic implementation called „Base64Cypher“. The „Base64Cypher“ is now the basic avoidance of having clear type passwords inside of the INI file. This affects the 3 common config sections: „MailSection“, „FtpSection“, „DatabaseSection“. You are also able to manually specify Cyphers for each Ini, Section and Entry instance itself (or remove them by setting „null“). Keep in mind, that this is just some sort of obfuscation, as everyone with some sort of experience could possibly crack those „client side only“ things (and some of them pretty easily). Feel free to implement your own „ICypher“ and specify that instead.
Security aspects
As freelance software developer I’ve seen (and wrote) a lot of code: My own, code from small companies and code from big companies as well. No matter which one you take, there can and will be bugs, fatal errors and sometimes even security problems.
Please make sure, that you keep in touch with common security aspects when for example saving user credentials on the users machine. You should never save like clear text passwords on a users system, mostly not even encrypted – because it can obviously be decrypted. Depending on your „encryption mechanism“ it can sometimes be harder and sometimes be easier – but the chance is never zero!
User spoofing / impersonation
If you actually create a nice program which grows in its user count, there will be some bad guys trying to harm you. The more it gets known, there more persons will know about where your program stores its like remembered login information. The most bad guys could just steal that file containing the users credentials impersonating that person without even knowing what his/her actual password is. For sure this could happen, if you are a small company as well…
Stealing passwords
Next to the previous consideration, it could get even worse. Someone stealing data from your pc – like the credentials file mentioned before – is one bad thing, but it could get even worse. If the bad guy is actually able to decode / access the saved password itself, he could try to use that in combination with a potentially saved email address as well. Congrats, now he may has access to the users actual mail address being able to reset different logins as well.
You should therefore take care of suggesting the use of a two factor authentication or you shouldn’t store the password in a decodable way anyways (or both). At least the entered password in some form of a hash and use this to authenticate him on the server / api side instead. This way, the attacker could steal the file for impersonation, but at least he wouldn’t know the users actual password, right? (Rainbow tables, salts, etc. aside).
Reporting problems
First and foremost – this NuGet package is free, so please keep in mind that I can’t provide some sort of free business level support. I mean, for sure one of my services is being a freelancer, but of course – like any other business – I need to charge money for that. You can send me an email anyways, but please don’t get confused if I may need some time to answer or to fix an existing problem. I will however try my best to do so.
Feel free to contact me if you have any suggestions or for example spotted some errors regarding the documentation as well.