Custom Configuration

Posted in .NET Framework // Posted at 2013. 8. 26. 15:13
728x90

닷넷 기반 응용프로그램은 (그것이 웹이든 콘솔 또는 윈도우 응용프로그램이든) 런타임에 구성 정보를 변경할 수 있도록 하는 설정파일이 존재한다.

 

웹 기반 응용프로그램의 경우 web.config, 윈도우 기반은 App.config 파일이 설정 파일 역할을 하게 된다.

 

1. Built-in AppSettings

이 설정파일에 기본적으로 제공되는 설정값 아닌 응용프로그램 자체에서 요구하는 별도의 설정 정보가 필요할 경우 대부분 <appSettings> 섹션에 의존하곤 한다.

 

<appSettings> 섹션은 '키-값' 형태의 설정 정보를 하나 이상 등록하여 사용할 수 있으며, AppSettingsReader 라는 닷넷 프레임워크가 기본으로 제공하는 리더기에 의해 엑세스 할 수 있다. 

 

* config 파일 설정:

<appSettings>
    <add key="configKeyUsingAppSetting" value="config[Value]UsingAppSetting"/>
</appSettings>

 

* conifig 값 액세스 :

AppSettingsReader appSettingsReader = new AppSettingsReader();
object valueOfAppSetting = appSettingsReader.GetValue("configKeyUsingAppSetting", typeof(string));
if (valueOfAppSetting != null)
{
     string value = valueOfAppSetting.ToString();
     Console.WriteLine(value);   //output: config[Value]UsingAppSetting

 

 

2. CustomSection Using Built-in Type

대부분의 소규모 프로젝트에서는 <appSettings> 섹션만을 사용해서 원하는 커스텀 설정값을 관리할 수 있을 것이다. 그러나 조금 더 구조적인 구분이 필요하거나 큰 프로젝트에서는 설정값의 성격에 따라 카테고리를 분리해서 관리해야 할 경우도 있다.

 

이럴 경우 제일 간단하게 접근할 수 있는 것이 내장된 타입을 이용한 커스텀 섹션을 정의하는 것이다.

이럴 경우 섹션값을 원하는 이름으로 정할 수 있기 때문에 응용프로그램의 설정 정보를 좀 더 스마트하게 구조화할 수 있게 된다.

 

커스텀 섹션을 정의하기 위해서는 <configSections> 요소에 원하는 이름의 섹션을 정의하면 된다.  이때 섹션의 타입을 닷넷 프레임워크가 제공하는 내장 타입을 지정할 수 있다. 

(사소한 주의사항은 <configSections>요소를 정의할 경우 <configuration>의 첫 번째 자식으로 위치해야 한다는 것이다)

 

실제 설정 값은 정의된 섹션의 이름으로 지정할 수 있으며 ConfigurationManager클래스를 통해 설정값을 액세스할 수 있게 된다. 다음 코드는 이러한 예를 보여준다.

 

* config 파일 설정:

<configSections>  
    <section name="mySection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>

 

<mySection>
    <add key="configKeyUsingCustomSection" value="config[Value]UsingCustomSection"/>
</mySection>

 

* config 값 액세스

NameValueCollection valueOfCustomSection =

                                    (NameValueCollection) ConfigurationManager.GetSection("mySection");

string value = valueOfCustomSection["configKeyUsingCustomSection"];
Console.WriteLine(value);  //output: config[Value]UsingCustomSection

 

 

3. CustomSection Using Custom Type

보다 큰 프로젝트인 경우 설정 값 역시 복잡한 구조를 띄게 된다. 앞서 살펴본 설정 정보들은 '키-값' 형태의 단순한 구조만을 지원하므로 복잡한 구조를 수용할 수 없다.

 

간혹, 키 값 형태의 구조틀안에서 한 단계 구조를 더 첨가(?)하기 위해 구분자를 활용하기도 한다.

대략 다음과 같은 형태를 띄게 되는데, 값을 정의하는 영역에 별도의 서브키를 부여하고 구분자로 이를 잘라내서 사용하곤 한다.

<add key="key" value="subKey-value1:subKey2-value2;subKey3-value3....."/>

 

이런 형태의 사용 패턴이 잘못 되었다고는 할 수 없지만, 구조적인 측면과 유지보수성 측면에서 봤을 때 좋은 점수를 줄 수 없다. 그 이유는 설정 값의 패턴 준수를 위한 신경을 써야 하는 점과 구분자로 사용된 문자가 실제 값과 중복되지 않음이 보장되어야 하는 점 등을 들 수 있겠다.

 

좀 더 객체지향적인 접근과 유지보수성을 좋게 하기 위해서는 커스텀 객체를 기반으로 설정 정보가 동작되도록 구성하는 것이 좋다.

 

간단한 샘플을 작성해 보자.

먼저 설정 정보를 추상화 시킨 클래스를 정의한다. 여기서는 '사람'의 정보를 추상화 시킨 클래스를 정의하겠다. 이때 이 클래스가 설정 정보로 사용되기 위해서는 ConfigurationSection을 상속받아야 한다.

그리고 클래스의 멤버들은 ConfigurationProperty Attribute로 선언하며 여기서 실제 config파일의 속성으로 사용할 이름과 기본값/필수유무에 대한 속성값들을 지정한다.

 

다음 코드는 이름, 나이, 결혼유무라는 속성을 가진 PersonInfoSection 클래스를 정의하고 있다.

public class PersonInfoSection : ConfigurationSection
{
        [ConfigurationProperty("isMarried", DefaultValue = "false", IsRequired = false)]
        public bool IsMarried
        {
            get
            {
                return (Boolean)this["isMarried"];
            }
            set
            {
                this["isMarried"] = value;
            }
        }

        [ConfigurationProperty("age", IsRequired = true)]
        public short Age
        {
            get
            {
                return (short)this["age"];
            }
            set
            {
                this["age"] = value;
            }
        }

        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }

}

 

이렇게 정의한 클래스 정보를 이전과 같이 커스텀 섹션에 추가하고 사용할 수 있게 된다.

* config 파일 설정

<configSections
      <section
          name="personInfoSection"
          type="CustomConfiguration.PersonInfoSection, CustomConfiguration"
       />  
  </configSections>

 

<personInfoSection name="박종명"  age="20" /> <!-- isMarried는 선택값으로 생략 가능 -->

 

* config 값 액세스

CustomConfiguration.PersonInfoSection personInfo =

  (CustomConfiguration.PersonInfoSection) ConfigurationManager.GetSection("personInfoSection");


 

Console.WriteLine(string.Format("이름:{0}, 나이{1}", personInfo.Name, personInfo.Age));

 


만일 PersonInfoSection의 멤버가 앞서 정의한 값(이름, 나이, 결혼유무)의 범위를 넘어서 또 다른 복합값을 가져야 할 경우 ConfigurationElement 상속받은 클래스를 정의하고 이 타입을 멤버로 사용하면 된다.


예를 위해서 PersonInfoSection 클래스에 자식 정보를 추가한다고 가정해 보자. 다음 코드와 같이 자식의 이름과 나이를 위한 클래스를 정의한다.


public class ChildrenElement : ConfigurationElement

{

        [ConfigurationProperty("name", DefaultValue="Arial", IsRequired = true)]

        public String Name

        {

            get

            {

                return (string)this["name"];

            }

            set

            {

                this["name"] = value;

            }

        }


        [ConfigurationProperty("age", IsRequired = true)]

        public short Age

        {

            get

            {

                return (short)this["age"];

            }

            set

            {

                this["age"] = value;

            }

        }

}


그리고 이렇게 정의한 클래스 타입을 PersonInfoSection 멤버로 사용할 수 있게 된다.

public class PersonInfoSection : ConfigurationSection

    {

       

        .........................


        [ConfigurationProperty("children")]

        public ChildrenElement Children

        {

            get

            {

                return (ChildrenElement)this["children"];

            }

            set

            { this["children"] = value; }

        }

    }

 

마지막으로 config파일에 자식 정보를 추가하고 이를 액세스 할 수 있다.

<personInfoSection name="박종명" age="20" isMarried="true">

    <children name="박길동" age="10" />

</personInfoSection>

 .....

Console.WriteLine(

 string.Format("자식이름:{0}, 자식나이{1}", personInfo.Children.Name, personInfo.Children.Age));


마지막으로 설정파일의 속성으로 사용할 클래스 멤버를 정의할 때,

ConfigurationProperty Attribute를 통해 기본 값(DefaultValue)이나 필수유무(IsRequired)를 지정할 수 있는 것과 더불어 [Type]Validator (StringValidator, IntegerValidator, ...) Attribute를 통해 유효한 문자, 숫자 범위등을 지정하는 등 좀 더 디테일한 제어가 가능하다.


마무리하며....

응용프로그램에서 필요로 하는 설정 정보를 분리하고 이를 잘 구조화해서 사용하면 코드의 직관성이나 유지보수성을 좋게 한다. 앞서 살펴본 대로 설정 정보를 구조화하는 몇 가지 방법이 있는데, 늘 사용하던 편리한 방법도 나름의 가치를 지니겠으나, 복잡한 설정 정보를 유지해야 할 경우 객체 기반으로 동작하는 코드 구성을 고민해 보길 바란다.