Reading/writing an INI file
Asked Answered
H

19

303

Is there any class in the .NET framework that can read/write standard .ini files:

[Section]
<keyname>=<value>
...

Delphi has the TIniFile component and I want to know if there is anything similar for C#?

Hurley answered 20/10, 2008 at 9:37 Comment(3)
RemObjects has a Delphi Prism library called ShineOn that ships a similar INI file class. But you need to have Delphi Prism to compile it for .NET from source as there is not yet a compiled assembly available. code.remobjects.com/p/shineonInvoke
Got the same problem and made my own library for parsing ini files: github.com/rickyah/ini-parser Hope it helpsWatchband
Just like Ricky I decided to make my own solution to this. Its available on: github.com/MarioZ/MadMilkman.IniSubdebutante
I
213

The creators of the .NET framework want you to use XML-based config files, rather than INI files. So no, there is no built-in mechanism for reading them.

There are third party solutions available, though.

Ingeingeberg answered 20/10, 2008 at 9:42 Comment(3)
@aloneguid I would argue that the large set of available features actually contributed to .NET config files ending up being strange behemoths with a lot of magic in them. They have become "code in the config file," and this leads to a lot of complexity, strange behaviors, and makes configuration management more difficult. (I'm looking at you, database "providers" and connection strings.) So INI files are also generally better for non-manual editing, as well.Redhanded
i like old method (P/Inovke) and you can use unicode with old method like this: File.WriteAllBytes(path, new byte[] { 0xFF, 0xFE });Inoculation
Good package but it could be better. It can not parse a value that contains '=' Or '\n' completelySpoil
P
303

Preface

Firstly, read this MSDN blog post on the limitations of INI files. If it suits your needs, read on.

This is a concise implementation I wrote, utilising the original Windows P/Invoke, so it is supported by all versions of Windows with .NET installed, (i.e. Windows 98 - Windows 11). I hereby release it into the public domain - you're free to use it commercially without attribution.

The tiny class

Add a new class called IniFile.cs to your project:

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

// Change this to match your program's normal namespace
namespace MyProg
{
    class IniFile   // revision 11
    {
        string Path;
        string EXE = Assembly.GetExecutingAssembly().GetName().Name;

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern long WritePrivateProfileString(string Section, string Key, string Value, string FilePath);

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern int GetPrivateProfileString(string Section, string Key, string Default, StringBuilder RetVal, int Size, string FilePath);

        public IniFile(string IniPath = null)
        {
            Path = new FileInfo(IniPath ?? EXE + ".ini").FullName;
        }

        public string Read(string Key, string Section = null)
        {
            var RetVal = new StringBuilder(255);
            GetPrivateProfileString(Section ?? EXE, Key, "", RetVal, 255, Path);
            return RetVal.ToString();
        }

        public void Write(string Key, string Value, string Section = null)
        {
            WritePrivateProfileString(Section ?? EXE, Key, Value, Path);
        }

        public void DeleteKey(string Key, string Section = null)
        {
            Write(Key, null, Section ?? EXE);
        }

        public void DeleteSection(string Section = null)
        {
            Write(null, null, Section ?? EXE);
        }

        public bool KeyExists(string Key, string Section = null)
        {
            return Read(Key, Section).Length > 0;
        }
    }
}

How to use it

Open the INI file in one of the 3 following ways:

// Creates or loads an INI file in the same directory as your executable
// named EXE.ini (where EXE is the name of your executable)
var MyIni = new IniFile();

// Or specify a specific name in the current dir
var MyIni = new IniFile("Settings.ini");

// Or specify a specific name in a specific dir
var MyIni = new IniFile(@"C:\Settings.ini");

You can write some values like so:

MyIni.Write("DefaultVolume", "100");
MyIni.Write("HomePage", "http://www.google.com");

To create a file like this:

[MyProg]
DefaultVolume=100
HomePage=http://www.google.com

To read the values out of the INI file:

var DefaultVolume = MyIni.Read("DefaultVolume");
var HomePage = MyIni.Read("HomePage");

Optionally, you can set [Section]'s:

MyIni.Write("DefaultVolume", "100", "Audio");
MyIni.Write("HomePage", "http://www.google.com", "Web");

To create a file like this:

[Audio]
DefaultVolume=100

[Web]
HomePage=http://www.google.com

You can also check for the existence of a key like so:

if(!MyIni.KeyExists("DefaultVolume", "Audio"))
{
    MyIni.Write("DefaultVolume", "100", "Audio");
}

You can delete a key like so:

MyIni.DeleteKey("DefaultVolume", "Audio");

You can also delete a whole section (including all keys) like so:

MyIni.DeleteSection("Web");

Please feel free to comment with any improvements!

Poach answered 16/2, 2013 at 2:52 Comment(24)
I'm little late, but it's missing GetSections() method.Enhance
Maybe a more traditional default would be per-application (not per-assembly) .ini files like Path.GetFullPath(IniPath ?? Path.ChangeExtension(Application.ExecutablePath, ".ini")).Azo
Really great ! Put it on github ?Overhead
What's the advantage of using P/Invoke other than backwards compatability?Leandroleaning
Due to the Assembly.GetExecutingAssembly().GetName().Name this doesn't play nicely when used in a common library as it'll expect the section header to be the name of the IniFile library rather than your application.Leandroleaning
@danny Beckett, nicely done. This is nearly exactly like the same as what I've used for the past um-years of .Net. Upgraded from old code years ago.Diazotize
Old now, and as much as I respect Raymond Chen, many of the limitations in that article were limitations of the specific INI library in Windows, and not the INI format itself. Others, like granular permissions, could be easily sidestepped via multiple files. An official, modernized INI library would be most-welcomed, even today.Penman
Reading is not working, Can you give another easy example reading value by providing key only?Bornite
@Bornite There must be some problem with your project or code. The function has worked for a long time, and the class code hasn't changed.Poach
To work the solution, section is must to specify in INI file as well in read() method.Seppuku
@JSushil Good spot! This only applies to .ini files where sections are used, however. I.e. you don't need to specify anything if you're using a flat-style without sections.Poach
This code works well! However I have a problem with the encoding. My INI file contains Japanese characters. When I use the above code to read it, the result contains strange chars. Can you help on this one?Amalgamation
How could i get the total keys of one section and foreach their keys to get the keys name and values? Thank you!Sounder
Why do you restrict the return buffer size of Read() to 255 characters, when the underlying API doesn't impose such limitation?Maximo
@Maximo That's a great question! What value are you able to use? I'm going to do another revision of this code soon, taking into account some of the comments above, so thanks!Poach
According to this answer, the maximum buffer size accepted by GetPrivateProfileString() appears to be 65536 (including null-terminator).Maximo
This will only work on Windows, and so breaks the multi-platform compatibility of your software. A high cost for such a small functionality.Backwash
it's a selfish plug, but the issues raised in commentary with this approach (cross-platform issues, limitations of the underlying win32 ini parser (of which I'm well aware)) are resolved with PeanutButter.INI (get it from nuget or find PeanutButter on github). I'm the author (hence the intro), but I'm responsive to issues. PB.INI also preserves commentary. Warning: the code is not super-pretty, but it works quite well and is actively maintained.Sayres
There is no error management if section or key has not be found. It could use the GetLastError() function to retrieve if an error occured. See learn.microsoft.com/en-us/windows/win32/api/winbase/…Azedarach
You should add this in github its very easy to use thanksHarleigh
@DannyBeckett Thanks a lot for this, I also needed a quick way to store some simple settings for a small app, otherwise I agree on the scoping of using .ini files nowadays. One small note, the Read(...) method does not work out of "the box" because you expect a Section to not be null there, so it kind-of makes using sections mandatory. I can use it like this for my purpose, but maybe you want to change that a bit. I see that also some other people mentioned they cannot read keys. Cheers!Tonietonight
awesome , thank you for the class , one thing i'm curious about is : if the user use something like = or " , some sort of an injection would that break the ini file ? , its not that the user will do it intentionally , but it can happen by mistake from users that are not aware of this thingsIamb
Reading not working. I need it for a ini file. Exist a solution?Deontology
great! relevant question/answer to this day! 14 year old question, amazingDevault
I
213

The creators of the .NET framework want you to use XML-based config files, rather than INI files. So no, there is no built-in mechanism for reading them.

There are third party solutions available, though.

Ingeingeberg answered 20/10, 2008 at 9:42 Comment(3)
@aloneguid I would argue that the large set of available features actually contributed to .NET config files ending up being strange behemoths with a lot of magic in them. They have become "code in the config file," and this leads to a lot of complexity, strange behaviors, and makes configuration management more difficult. (I'm looking at you, database "providers" and connection strings.) So INI files are also generally better for non-manual editing, as well.Redhanded
i like old method (P/Inovke) and you can use unicode with old method like this: File.WriteAllBytes(path, new byte[] { 0xFF, 0xFE });Inoculation
Good package but it could be better. It can not parse a value that contains '=' Or '\n' completelySpoil
C
67

This article on CodeProject "An INI file handling class using C#" should help.

The author created a C# class "Ini" which exposes two functions from KERNEL32.dll. These functions are: WritePrivateProfileString and GetPrivateProfileString. You will need two namespaces: System.Runtime.InteropServices and System.Text.

Steps to use the Ini class

In your project namespace definition add

using INI;

Create a INIFile like this

INIFile ini = new INIFile("C:\\test.ini");

Use IniWriteValue to write a new value to a specific key in a section or use IniReadValue to read a value FROM a key in a specific Section.

Note: if you're beginning from scratch, you could read this MSDN article: How to: Add Application Configuration Files to C# Projects. It's a better way for configuring your application.

Chough answered 20/10, 2008 at 9:42 Comment(6)
I want to read complete INI file. How to do the same instead of reading section,keyAphorism
this worked for me, and then stopped working from another point. No idea still what went different under the hoodMacrospore
Watch out using this deprecated Win32 API functions. More info: #11452141Ibanez
I used this approach for awhile, but security enhancements starting in Win7 have pretty much killed this for me. You can still use this approach, but you will have store the .ini in ProgramData and have your app read / write there.Comedietta
Do not save application configuration ini files in ProgramData. They do not belong in either the Registry or ProgramData. Config files are supposed to be in the LocalApplicationData folders.Longley
Simple INI file reader/writer which served my purpose without bloated library code. :)Bozeman
G
49

I found this simple implementation:

http://bytes.com/topic/net/insights/797169-reading-parsing-ini-file-c

Works well for what I need.

Here is how you use it:

public class TestParser
{
    public static void Main()
    {
        IniParser parser = new IniParser(@"C:\test.ini");

        String newMessage;

        newMessage = parser.GetSetting("appsettings", "msgpart1");
        newMessage += parser.GetSetting("appsettings", "msgpart2");
        newMessage += parser.GetSetting("punctuation", "ex");

        //Returns "Hello World!"
        Console.WriteLine(newMessage);
        Console.ReadLine();
    }
}

Here is the code:

using System;
using System.IO;
using System.Collections;

public class IniParser
{
    private Hashtable keyPairs = new Hashtable();
    private String iniFilePath;

    private struct SectionPair
    {
        public String Section;
        public String Key;
    }

    /// <summary>
    /// Opens the INI file at the given path and enumerates the values in the IniParser.
    /// </summary>
    /// <param name="iniPath">Full path to INI file.</param>
    public IniParser(String iniPath)
    {
        TextReader iniFile = null;
        String strLine = null;
        String currentRoot = null;
        String[] keyPair = null;

        iniFilePath = iniPath;

        if (File.Exists(iniPath))
        {
            try
            {
                iniFile = new StreamReader(iniPath);

                strLine = iniFile.ReadLine();

                while (strLine != null)
                {
                    strLine = strLine.Trim().ToUpper();

                    if (strLine != "")
                    {
                        if (strLine.StartsWith("[") && strLine.EndsWith("]"))
                        {
                            currentRoot = strLine.Substring(1, strLine.Length - 2);
                        }
                        else
                        {
                            keyPair = strLine.Split(new char[] { '=' }, 2);

                            SectionPair sectionPair;
                            String value = null;

                            if (currentRoot == null)
                                currentRoot = "ROOT";

                            sectionPair.Section = currentRoot;
                            sectionPair.Key = keyPair[0];

                            if (keyPair.Length > 1)
                                value = keyPair[1];

                            keyPairs.Add(sectionPair, value);
                        }
                    }

                    strLine = iniFile.ReadLine();
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (iniFile != null)
                    iniFile.Close();
            }
        }
        else
            throw new FileNotFoundException("Unable to locate " + iniPath);

    }

    /// <summary>
    /// Returns the value for the given section, key pair.
    /// </summary>
    /// <param name="sectionName">Section name.</param>
    /// <param name="settingName">Key name.</param>
    public String GetSetting(String sectionName, String settingName)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        return (String)keyPairs[sectionPair];
    }

    /// <summary>
    /// Enumerates all lines for given section.
    /// </summary>
    /// <param name="sectionName">Section to enum.</param>
    public String[] EnumSection(String sectionName)
    {
        ArrayList tmpArray = new ArrayList();

        foreach (SectionPair pair in keyPairs.Keys)
        {
            if (pair.Section == sectionName.ToUpper())
                tmpArray.Add(pair.Key);
        }

        return (String[])tmpArray.ToArray(typeof(String));
    }

    /// <summary>
    /// Adds or replaces a setting to the table to be saved.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    /// <param name="settingValue">Value of key.</param>
    public void AddSetting(String sectionName, String settingName, String settingValue)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        if (keyPairs.ContainsKey(sectionPair))
            keyPairs.Remove(sectionPair);

        keyPairs.Add(sectionPair, settingValue);
    }

    /// <summary>
    /// Adds or replaces a setting to the table to be saved with a null value.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    public void AddSetting(String sectionName, String settingName)
    {
        AddSetting(sectionName, settingName, null);
    }

    /// <summary>
    /// Remove a setting.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    public void DeleteSetting(String sectionName, String settingName)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        if (keyPairs.ContainsKey(sectionPair))
            keyPairs.Remove(sectionPair);
    }

    /// <summary>
    /// Save settings to new file.
    /// </summary>
    /// <param name="newFilePath">New file path.</param>
    public void SaveSettings(String newFilePath)
    {
        ArrayList sections = new ArrayList();
        String tmpValue = "";
        String strToSave = "";

        foreach (SectionPair sectionPair in keyPairs.Keys)
        {
            if (!sections.Contains(sectionPair.Section))
                sections.Add(sectionPair.Section);
        }

        foreach (String section in sections)
        {
            strToSave += ("[" + section + "]\r\n");

            foreach (SectionPair sectionPair in keyPairs.Keys)
            {
                if (sectionPair.Section == section)
                {
                    tmpValue = (String)keyPairs[sectionPair];

                    if (tmpValue != null)
                        tmpValue = "=" + tmpValue;

                    strToSave += (sectionPair.Key + tmpValue + "\r\n");
                }
            }

            strToSave += "\r\n";
        }

        try
        {
            TextWriter tw = new StreamWriter(newFilePath);
            tw.Write(strToSave);
            tw.Close();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// Save settings back to ini file.
    /// </summary>
    public void SaveSettings()
    {
        SaveSettings(iniFilePath);
    }
}
Giovannagiovanni answered 18/2, 2011 at 4:8 Comment(0)
L
26

The code in joerage's answer is inspiring.

Unfortunately, it changes the character casing of the keys and does not handle comments. So I wrote something that should be robust enough to read (only) very dirty INI files and allows to retrieve keys as they are.

It uses some LINQ, a nested case insensitive string dictionary to store sections, keys and values, and read the file in one go.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class IniReader
{
    Dictionary<string, Dictionary<string, string>> ini = new Dictionary<string, Dictionary<string, string>>(StringComparer.InvariantCultureIgnoreCase);

    public IniReader(string file)
    {
        var txt = File.ReadAllText(file);

        Dictionary<string, string> currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

        ini[""] = currentSection;

        foreach(var line in txt.Split(new[]{"\n"}, StringSplitOptions.RemoveEmptyEntries)
                               .Where(t => !string.IsNullOrWhiteSpace(t))
                               .Select(t => t.Trim()))
        {
            if (line.StartsWith(";"))
                continue;

            if (line.StartsWith("[") && line.EndsWith("]"))
            {
                currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
                ini[line.Substring(1, line.LastIndexOf("]") - 1)] = currentSection;
                continue;
            }

            var idx = line.IndexOf("=");
            if (idx == -1)
                currentSection[line] = "";
            else
                currentSection[line.Substring(0, idx)] = line.Substring(idx + 1);
        }
    }

    public string GetValue(string key)
    {
        return GetValue(key, "", "");
    }

    public string GetValue(string key, string section)
    {
        return GetValue(key, section, "");
    }

    public string GetValue(string key, string section, string @default)
    {
        if (!ini.ContainsKey(section))
            return @default;

        if (!ini[section].ContainsKey(key))
            return @default;

        return ini[section][key];
    }

    public string[] GetKeys(string section)
    {
        if (!ini.ContainsKey(section))
            return new string[0];

        return ini[section].Keys.ToArray();
    }

    public string[] GetSections()
    {
        return ini.Keys.Where(t => t != "").ToArray();
    }
}
Limnology answered 6/6, 2013 at 21:47 Comment(3)
and thank you for not putting that catch (Exception ex) { throw ex; } in thereSchwejda
Good! At least some changes are required to work better. Line 16: ini[""] = currentSection; To: //ini[""] = currentSection; This must be removed as every time the first element [0] will be an empty segment due to this initialization. Line 36: currentSection[line.Substring(0, idx)] = line.Substring(idx + 1); To: currentSection[line.Substring(0, idx).Trim()] = line.Substring(idx + 1).Trim(); Key and values should be independently trimmed, not only on the line Trim. In INI like configuration files usually who add K->V pairs tend to align these equals inside sections. Thank you!Hoeve
Wew been à long time. Thanks a lot for your suggestions. They all make sense and deserves this code to have a good refresh.Limnology
W
16

I want to introduce an IniParser library I've created completely in c#, so it contains no dependencies in any OS, which makes it Mono compatible. Open Source with MIT license -so it can be used in any code.

You can check out the source in GitHub, and it is also available as a NuGet package

It's heavily configurable, and really simple to use.

Sorry for the shameless plug but I hope it can be of help of anyone revisiting this answer.

Watchband answered 21/4, 2014 at 18:22 Comment(0)
C
13

If you only need read access and not write access and you are using the Microsoft.Extensions.Confiuration (comes bundled in by default with ASP.NET Core but works with regular programs too) you can use the NuGet package Microsoft.Extensions.Configuration.Ini to import ini files in to your configuration settings.

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddIniFile("SomeConfig.ini", optional: false);
    Configuration = builder.Build();
}
Contuse answered 6/7, 2017 at 5:3 Comment(2)
Just to add that you then get keys with Configuration["keyname"]Entwine
@scott the issue I'm having is for whatever reason IIS does not recognize it when the app is running. it is deployed, and there, but is not being consumed. HTTP 500.30 returned and the IIS App log says "the configuration file was not found and is not optional."Macrocosm
L
6

If you want just a simple reader without sections and any other dlls here is simple solution:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tool
{
    public class Config
    {
        Dictionary <string, string> values;
        public Config (string path)
        {
            values = File.ReadLines(path)
            .Where(line => (!String.IsNullOrWhiteSpace(line) && !line.StartsWith("#")))
            .Select(line => line.Split(new char[] { '=' }, 2, 0))
            .ToDictionary(parts => parts[0].Trim(), parts => parts.Length>1?parts[1].Trim():null);
        }
        public string Value (string name, string value=null)
        {
            if (values!=null && values.ContainsKey(name))
            {
                return values[name];
            }
            return value;
        }
    }
}

Usage sample:

    file = new Tool.Config (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\config.ini");
    command = file.Value ("command");
    action = file.Value ("action");
    string value;
    //second parameter is default value if no key found with this name
    value = file.Value("debug","true");
    this.debug = (value.ToLower()=="true" || value== "1");
    value = file.Value("plain", "false");
    this.plain = (value.ToLower() == "true" || value == "1");

Config file content meanwhile (as you see supports # symbol for line comment):

#command to run
command = php

#default script
action = index.php

#debug mode
#debug = true

#plain text mode
#plain = false

#icon = favico.ico
Latrell answered 12/6, 2016 at 9:16 Comment(0)
S
5

PeanutButter.INI is a Nuget-packaged class for INI files manipulation. It supports read/write, including comments – your comments are preserved on write. It appears to be reasonably popular, is tested and easy to use. It's also totally free and open-source.

Disclaimer: I am the author of PeanutButter.INI.

Sayres answered 21/7, 2017 at 17:37 Comment(2)
Could you please provide a link to PeanutButter.INI documentation?Bract
Check github.com/fluffynuts/PeanutButter/blob/master/source/INI/…Sayres
C
4

Usually, when you create applications using C# and the .NET framework, you will not use INI files. It is more common to store settings in an XML-based configuration file or in the registry. However, if your software shares settings with a legacy application it may be easier to use its configuration file, rather than duplicating the information elsewhere.

The .NET framework does not support the use of INI files directly. However, you can use Windows API functions with Platform Invocation Services (P/Invoke) to write to and read from the files. In this link we create a class that represents INI files and uses Windows API functions to manipulate them. Please go through the following link.

Reading and Writing INI Files

Carrageen answered 2/4, 2012 at 6:14 Comment(3)
Stay out of the Registry! Application configuration data should not be saved in the Registry.Longley
@deegee: Funny, since Microsoft says INI file are deprecated in favor of the registry.Caducity
@ThomasWeller - I'm not here to argue with people. The Registry should never be used unless it is absolutely necessary. And even then it should be reserved for Microsoft Windows use only. Virtually no third-party software cleans up their Registry keys when you uninstall their software, leaving the Registry in a state of mess, which they should have stayed out of to begin with. INI files, XML files, JSON files, and other configuration file formats designed specifically for this function should be placed into the AppData folder where they are SUPPOSED to go.Longley
U
3

Try this method:

public static Dictionary<string, string> ParseIniDataWithSections(string[] iniData)
{
    var dict = new Dictionary<string, string>();
    var rows = iniData.Where(t => 
        !String.IsNullOrEmpty(t.Trim()) && !t.StartsWith(";") && (t.Contains('[') || t.Contains('=')));
    if (rows == null || rows.Count() == 0) return dict;
    string section = "";
    foreach (string row in rows)
    {
        string rw = row.TrimStart();
        if (rw.StartsWith("["))
            section = rw.TrimStart('[').TrimEnd(']');
        else
        {
            int index = rw.IndexOf('=');
            dict[section + "-" + rw.Substring(0, index).Trim()] = rw.Substring(index+1).Trim().Trim('"');
        }
    }
    return dict;
}

It creates the dictionary where the key is "-". You can load it like this:

var dict = ParseIniDataWithSections(File.ReadAllLines(fileName));
Unpeopled answered 7/2, 2017 at 18:51 Comment(0)
B
3

I'm late to join the party, but I had the same issue today and I've written the following implementation:

using System.Text.RegularExpressions;

static bool match(this string str, string pat, out Match m) =>
    (m = Regex.Match(str, pat, RegexOptions.IgnoreCase)).Success;

static void Main()
{
    Dictionary<string, Dictionary<string, string>> ini = new Dictionary<string, Dictionary<string, string>>();
    string section = "";

    foreach (string line in File.ReadAllLines(.........)) // read from file
    {
        string ln = (line.Contains('#') ? line.Remove(line.IndexOf('#')) : line).Trim();

        if (ln.match(@"^[ \t]*\[(?<sec>[\w\-]+)\]", out Match m))
            section = m.Groups["sec"].ToString();
        else if (ln.match(@"^[ \t]*(?<prop>[\w\-]+)\=(?<val>.*)", out m))
        {
            if (!ini.ContainsKey(section))
                ini[section] = new Dictionary<string, string>();

            ini[section][m.Groups["prop"].ToString()] = m.Groups["val"].ToString();
        }
    }


    // access the ini file as follows:
    string content = ini["section"]["property"];
}

It must be noted, that this implementation does not handle sections or properties which are not found. To achieve this, you should extend the Dictionary<,>-class to handle unfound keys.


To serialize an instance of Dictionary<string, Dictionary<string, string>> to an .ini-file, I use the following code:

string targetpath = .........;
Dictionary<string, Dictionary<string, string>> ini = ........;
StringBuilder sb = new StringBuilder();

foreach (string section in ini.Keys)
{
    sb.AppendLine($"[{section}]");

    foreach (string property in ini[section].Keys)
        sb.AppendLine($"{property}={ini[section][property]");
}

File.WriteAllText(targetpath, sb.ToString());
Boong answered 18/8, 2017 at 17:20 Comment(0)
L
2

There is an Ini Parser available in CommonLibrary.NET

This has various very convenient overloads for getting sections/values and is very light weight.

Ledezma answered 28/1, 2010 at 6:27 Comment(2)
In case it's not obvious from looking at the top level of the library (it wasn't obvious to me!), the IniDcoument class et al are in ComLib.IO.Zinnes
For anyone looking at this route, CommonLibrary.NET does not seem to follow .INI conventions. It uses a colon ":" as the delimiter instead of equals sign, and it does not handle comments (beginning a line with a semi-colon or pound sign will cause parsing to fail).Yokefellow
F
1

Here is my own version, using regular expressions. This code assumes that each section name is unique - if however this is not true - it makes sense to replace Dictionary with List. This function supports .ini file commenting, starting from ';' character. Section starts normally [section], and key value pairs also comes normally "key = value". Same assumption as for sections - key name is unique.

/// <summary>
/// Loads .ini file into dictionary.
/// </summary>
public static Dictionary<String, Dictionary<String, String>> loadIni(String file)
{
    Dictionary<String, Dictionary<String, String>> d = new Dictionary<string, Dictionary<string, string>>();

    String ini = File.ReadAllText(file);

    // Remove comments, preserve linefeeds, if end-user needs to count line number.
    ini = Regex.Replace(ini, @"^\s*;.*$", "", RegexOptions.Multiline);

    // Pick up all lines from first section to another section
    foreach (Match m in Regex.Matches(ini, "(^|[\r\n])\\[([^\r\n]*)\\][\r\n]+(.*?)(\\[([^\r\n]*)\\][\r\n]+|$)", RegexOptions.Singleline))
    {
        String sectionName = m.Groups[2].Value;
        Dictionary<String, String> lines = new Dictionary<String, String>();

        // Pick up "key = value" kind of syntax.
        foreach (Match l in Regex.Matches(ini, @"^\s*(.*?)\s*=\s*(.*?)\s*$", RegexOptions.Multiline))
        {
            String key = l.Groups[1].Value;
            String value = l.Groups[2].Value;

            // Open up quotation if any.
            value = Regex.Replace(value, "^\"(.*)\"$", "$1");

            if (!lines.ContainsKey(key))
                lines[key] = value;
        }

        if (!d.ContainsKey(sectionName))
            d[sectionName] = lines;
    }

    return d;
}
Freida answered 14/10, 2016 at 20:45 Comment(2)
That function doesn't work, for me: It forgets one section in two. I tried with and without empty lines before [Section] .Zephyrus
can you copy example of your .ini which does not work ?Freida
F
1

Check my github, I think it will be help you:

https://github.com/MhyrAskri/Ini-Reader

IniReader.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace yourNamespace
{
    public class Ini
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="IniFile"/> class.
        /// </summary>
        /// <param name="file">The initialization file path.</param>
        /// <param name="commentDelimiter">The comment delimiter string (default value is ";").
        /// </param>
        public Ini(string file)
        {
            TheFile = file;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IniFile"/> class.
        /// </summary>
        //public Ini()
        //{
        //    CommentDelimiter = ";";
        //}

        /// <summary>
        /// The comment delimiter string (default value is ";").
        /// </summary>
        //public string CommentDelimiter { get; set; }

        private string theFile = null;

        /// <summary>
        /// The initialization file path.
        /// </summary>
        public string TheFile
        {
            get
            {
                return theFile;
            }
            set
            {
                theFile = null;
                dictionary.Clear();
                if (File.Exists(value))
                {
                    theFile = value;
                    using StreamReader sr = new StreamReader(theFile);
                    string line, section = "";
                    while ((line = sr.ReadLine()) != null)
                    {
                        line = line.Trim();
                        if (line.Length == 0) continue;  // empty line
                                                         //if (line.StartsWith(CommentDelimiter))
                                                         //    continue;  // comment

                        if (line.StartsWith("[") && line.Contains("]"))  // [section]
                        {
                            int index = line.IndexOf(']');
                            section = line[1..index].Trim();
                            continue;
                        }

                        if (line.Contains("="))  // key=value
                        {
                            int index = line.IndexOf('=');
                            string key = line.Substring(0, index).Trim();
                            string val = line[(index + 1)..].Trim();
                            string key2 = String.Format("[{0}]{1}", section, key).ToLower();

                            if (val.StartsWith("\"") && val.EndsWith("\""))  // strip quotes
                                val = val[1..(val.Length - 2)];

                            if (dictionary.ContainsKey(key2))  // multiple values can share the same key
                            {
                                index = 1;
                                string key3;
                                while (true)
                                {
                                    key3 = String.Format("{0}~{1}", key2, ++index);
                                    if (!dictionary.ContainsKey(key3))
                                    {
                                        dictionary.Add(key3, val);
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                dictionary.Add(key2, val);
                            }
                        }
                    }
                }
            }
        }

        // "[section]key"   -> "value1"
        // "[section]key~2" -> "value2"
        // "[section]key~3" -> "value3"
        private readonly Dictionary<string, string> dictionary = new Dictionary<string, string>();

        private bool TryGetValue(string section, string key, out string value)
        {
            string key2;
            if (section.StartsWith("["))
                key2 = String.Format("{0}{1}", section, key);
            else
                key2 = String.Format("[{0}]{1}", section, key);

            return dictionary.TryGetValue(key2.ToLower(), out value);
        }

        /// <summary>
        /// Gets a string value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>The value.</returns>
        /// <seealso cref="GetAllValues"/>
        public string GetValue(string section, string key, string defaultValue = "")
        {
            if (!TryGetValue(section, key, out string value))
                return defaultValue;

            return value;
        }

        /// <summary>
        /// Gets a string value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns>The value.</returns>
        /// <seealso cref="GetValue"/>
        public string this[string section, string key]
        {
            get
            {
                return GetValue(section, key);
            }
        }

        /// <summary>
        /// Gets an integer value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="minValue">Optional minimum value to be enforced.</param>
        /// <param name="maxValue">Optional maximum value to be enforced.</param>
        /// <returns>The value.</returns>
        public int GetInteger(string section, string key, int defaultValue = 0,
            int minValue = int.MinValue, int maxValue = int.MaxValue)
        {
            if (!TryGetValue(section, key, out string stringValue))
                return defaultValue;

            if (!int.TryParse(stringValue, out int value))
            {
                if (!double.TryParse(stringValue, out double dvalue))
                    return defaultValue;
                value = (int)dvalue;
            }

            if (value < minValue)
                value = minValue;
            if (value > maxValue)
                value = maxValue;
            return value;
        }

        /// <summary>
        /// Gets a double floating-point value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="minValue">Optional minimum value to be enforced.</param>
        /// <param name="maxValue">Optional maximum value to be enforced.</param>
        /// <returns>The value.</returns>
        //public double GetDouble(string section, string key, double defaultValue = 0,
        //    double minValue = double.MinValue, double maxValue = double.MaxValue)
        //{
        //    string stringValue;
        //    if (!TryGetValue(section, key, out stringValue))
        //        return defaultValue;

        //    double value;
        //    if (!double.TryParse(stringValue, out value))
        //        return defaultValue;

        //    if (value < minValue)
        //        value = minValue;
        //    if (value > maxValue)
        //        value = maxValue;
        //    return value;
        //}

        /// <summary>
        /// Gets a boolean value by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>The value.</returns>
        public bool GetBoolean(string section, string key, bool defaultValue = false)
        {
            if (!TryGetValue(section, key, out string stringValue))
                return defaultValue;

            return (stringValue != "0" && !stringValue.StartsWith("f", true, null));
        }

        /// <summary>
        /// Gets an array of string values by section and key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns>The array of values, or null if none found.</returns>
        /// <seealso cref="GetValue"/>
        //public string[] GetAllValues(string section, string key)
        //{
        //    string key2, key3, value;
        //    if (section.StartsWith("["))
        //        key2 = String.Format("{0}{1}", section, key).ToLower();
        //    else
        //        key2 = String.Format("[{0}]{1}", section, key).ToLower();

        //    if (!dictionary.TryGetValue(key2, out value))
        //        return null;

        //    List<string> values = new List<string>();
        //    values.Add(value);
        //    int index = 1;
        //    while (true)
        //    {
        //        key3 = String.Format("{0}~{1}", key2, ++index);
        //        if (!dictionary.TryGetValue(key3, out value))
        //            break;
        //        values.Add(value);
        //    }

        //    return values.ToArray();
        //}
    }
}
Factor answered 26/9, 2023 at 23:42 Comment(2)
Welcome to Stack Overflow! While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value.Realist
@Realist Thanks for your help and information.Factor
D
0

I needed a way to simply read values from an ini file that is cross-platform (Linux, Mac, Windows) too (something that doesn't depend upon Kernel32 & GetPrivateProfileSection).

I ended up using some code from this article but I dumbed it down quite a bit and made it run from a .NET Core (8.x) console app.

I also added a test.ini file so you can download it and run it and see the results quickly.

You can grab the one source file IniFileReader.cs and include it in any project so you can easily read the values out of your ini files.

Here's a quick look at the source which you can get at my github repo:

Just pass in the ini file (full path or relative) and new up an IniFileReader and it'll parse the entire file so you can grab any value or iterate over them.

class IniFileReader
{
    private string FileName {get;set;}

    private Dictionary<string, Dictionary<string, string>> m_Sections = new Dictionary<string, Dictionary<string, string>>();

    public IniFileReader(string fileName)
    {
        FileName = fileName;
        ParseFile();
    }

    public void DisplayAllSections(){
        
        foreach (string sectionKey in m_Sections.Keys){
            Console.WriteLine($"[{sectionKey}]");
            Dictionary<string,string> keyValuePairs = null;
            m_Sections.TryGetValue(sectionKey, out keyValuePairs);
            Console.WriteLine($"Values in section: {keyValuePairs.Count}");
            foreach (string k in keyValuePairs.Keys){
                 string value = null;
                 keyValuePairs.TryGetValue(k,out value);
                 Console.WriteLine($"{k} : {value}");
            }
            Console.WriteLine();
        }
    }
    private string ParseSectionName(string Line)
    {
        if (!Line.StartsWith("[")) return null;
        if (!Line.EndsWith("]")) return null;
        if (Line.Length < 3) return null;
        return Line.Substring(1, Line.Length - 2);
    }

    private bool ParseKeyValuePair(string Line, ref string Key, ref string Value)
    {
        int i;
        if ((i = Line.IndexOf('=')) <= 0) return false;
        
        int j = Line.Length - i - 1;
        Key = Line.Substring(0, i).Trim();
        if (Key.Length <= 0) return false;

        Value = (j > 0) ? (Line.Substring(i + 1, j).Trim()) : ("");
        return true;
    }

    public string GetValue(string SectionName, string Key, string DefaultValue="")
    {
        // *** Check if the section exists ***
        Dictionary<string, string> Section;
        if (!m_Sections.TryGetValue(SectionName, out Section)) return DefaultValue;

        // *** Check if the key exists ***
        string Value;
        if (!Section.TryGetValue(Key, out Value)) return DefaultValue;
    
        // *** Return the found value ***
        return Value;
    }

    public void ParseFile(){
        StreamReader sr = null;
        try
        {
            // *** Clear local cache ***
            m_Sections.Clear();

            // *** Open the INI file ***
            try
            {
                sr = new StreamReader(FileName);
            }
            catch (FileNotFoundException)
            {
                return;
            }

            Dictionary<string, string> CurrentSection = null;
            string s;
                string SectionName;
                string Key = null;
                string Value = null;
            while ((s = sr.ReadLine()) != null)
            {
                s = s.Trim();

                    SectionName = ParseSectionName(s);
                    if (SectionName != null)
                {
                    // *** Only first occurrence of a section is loaded - duplicates ignored***
                    if (m_Sections.ContainsKey(SectionName))
                    {
                        CurrentSection = null;
                    }
                    else
                    {
                        CurrentSection = new Dictionary<string, string>();
                            m_Sections.Add(SectionName, CurrentSection);
                    }
                }
                else if (CurrentSection != null)
                {
                        // *** Check for key+value pair ***
                        if (ParseKeyValuePair(s, ref Key, ref Value))
                        {
                            // *** Only first occurrence of a key is loaded - duplicates ignored ***
                            if (!CurrentSection.ContainsKey(Key))
                            {
                                CurrentSection.Add(Key, Value);
                            }
                        }
                }
            }
        }
        finally
        {
            if (sr != null) sr.Close();
            sr = null;
        }
    }
}

Output Looks Like

output after parsing ini

Darr answered 25/3, 2024 at 21:23 Comment(0)
E
-1

If you don't need bells and whistles (ie sections) here's a one liner:

List<(string, string)> ini = File.ReadLines(filename)
  .Select(s => {
     var spl = s.Split('=', 2);
     return spl.Length == 2 ? (spl[0], spl[1]) : (s, "");
   })
   .Select(vt => (vt.Item1.Trim(), vt.Item2.Trim()))
   .Where(vt => vt.Item1 != "")
   .ToList();

To write:

File.WriteAllLines(filename, ini.Select(vt => $"{vt.Item1}={vt.Item2}"));

(if you don't care about duplicates use .ToDictionary() instead of .ToList() for easier access)

Entwine answered 9/6, 2022 at 10:2 Comment(0)
R
-4

Here is my class, works like a charm :

public static class IniFileManager
{


    [DllImport("kernel32")]
    private static extern long WritePrivateProfileString(string section,
        string key, string val, string filePath);
    [DllImport("kernel32")]
    private static extern int GetPrivateProfileString(string section,
             string key, string def, StringBuilder retVal,
        int size, string filePath);
    [DllImport("kernel32.dll")]
    private static extern int GetPrivateProfileSection(string lpAppName,
             byte[] lpszReturnBuffer, int nSize, string lpFileName);


    /// <summary>
    /// Write Data to the INI File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// Section name
    /// <PARAM name="Key"></PARAM>
    /// Key Name
    /// <PARAM name="Value"></PARAM>
    /// Value Name
    public static void IniWriteValue(string sPath,string Section, string Key, string Value)
    {
        WritePrivateProfileString(Section, Key, Value, sPath);
    }

    /// <summary>
    /// Read Data Value From the Ini File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// <PARAM name="Key"></PARAM>
    /// <PARAM name="Path"></PARAM>
    /// <returns></returns>
    public static string IniReadValue(string sPath,string Section, string Key)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(Section, Key, "", temp,
                                        255, sPath);
        return temp.ToString();

    }

}

The use is obviouse since its a static class, just call IniFileManager.IniWriteValue for readsing a section or IniFileManager.IniReadValue for reading a section.

Ribband answered 1/10, 2018 at 12:17 Comment(2)
This approach has already been shown and explained in another answer. What does your answer add that's not covered by that one?Glebe
Beware that it works only if .ini file is saved in UNICODE (16bit LE). Use Notepad++ to convert the text to unicode, because if you save it in UTF-8 wont work. Also ANSI is acceptable, but you cannot read accented lettersMagda
L
-8

You should read and write data from xml files since you can save a whole object to xml and also you can populate a object from a saved xml. It is better an easy to manipulate objects.

Here is how to do it: Write Object Data to an XML File: https://msdn.microsoft.com/en-us/library/ms172873.aspx Read Object Data from an XML File: https://msdn.microsoft.com/en-us/library/ms172872.aspx

Laky answered 13/1, 2016 at 1:43 Comment(3)
Links to external resources are encouraged, but please add context around the link so your fellow users will have some idea what it is and why it’s there. Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline.Agamic
I believe that the links titles are very clear about its references/context. If you think that´s not enough feel free to edit it.Laky
Does not address the actual question.Mumps

© 2022 - 2025 — McMap. All rights reserved.