Texts in multi-language

Texts in multi-language using C#:


<?xml version="1.0" encoding="utf-8" ?>
<Test>
  <MSG Code="0">
    <en>Success.</en>
    <zh>成功.</zh>
  </MSG>
  <MSG Code="100001">
    <en>Some error.</en>
    <zh>錯誤.</zh>
  </MSG>
</Test>

 


public enum TestErrorCode
{
  [Description("Success")] Succ = 0,
  [Description("Some error")] SomeError = 100001
}

public enum TestLang
{
  [Description("english")] en = 0,
  [Description("中文")] zh = 1
}

public class LangSer
{
  public class Book
  {
    /// <summary>
    /// Dictionary<Error code, msg>
    /// </summary>
    public Dictionary<string, string> Errors { get; set; }
  }

  /// <summary>
  /// Dictionary<Language type, book>
  /// </summary>
  public Dictionary<string, Book> Books { get; set; }

  public bool HasData()
  {
    return Books != null && Books.Count > 0;
  }

  public bool TryGetText(TestErrorCode error, TestLang specLang, TestLang defaultLang, out string result)
  {
    result = string.Empty;

    //### Get by input language.
    string errorCode = ((int)error).ToString();
    string langText;
    if (TryGetText(errorCode, specLang, out langText))
    {
      result = langText;
      return true;
    }

    //### Get by default language.
    string defaultLangText;
    if (specLang != defaultLang
      && TryGetText(errorCode, defaultLang, out defaultLangText))
    {
      result = defaultLangText;
      return false;
    }

    //### Get error desc.
    result = Enums.GetEnumDesc(error);
    return false;
  }

  private bool TryGetText(string errorCode, TestLang lang, out string text)
  {
    text = string.Empty;

    Book langBook;
    string langType = lang.ToString();
    if (!TryGetValue(Books, langType, out langBook)
      || !TryGetValue(langBook.Errors, errorCode, out text))
    {
      return false;
    }

    return true;
  }

  private bool TryGetValue(Dictionary<string, Book> dict, string key, out Book val)
  {
    return dict.TryGetValue(key, out val) && val != null && val.Errors != null && val.Errors.Count > 0;
  }

  private bool TryGetValue(Dictionary<string, string> dict, string key, out string val)
  {
    return dict.TryGetValue(key, out val) && !string.IsNullOrWhiteSpace(val);
  }
}

public sealed class Test
{
  private readonly string _resPath = AppDomain.CurrentDomain.BaseDirectory + "Settings/TestError.xml";
  private readonly TestLang _defaultLang = TestLang.en;
  private readonly string _cacheKey = "TestError";
  private readonly Logger _logger = LogManager.GetLogger("TestLogger");
  private static readonly Lazy<Test> _instanceHolder = new Lazy<Test>(() => new Test());
  private LangSer _res = null;

  public static Test RES
  {
    get
    {
      return _instanceHolder.Value;
    }
  }

  public TestLang ParseLang(string langType)
  {
    if (!string.IsNullOrWhiteSpace(langType))
    {
      langType = langType.Trim();

      foreach (TestLang lang in (TestLang[])Enum.GetValues(typeof(TestLang)))
      {
        if (lang.ToString().Equals(langType, StringComparison.CurrentCultureIgnoreCase))
        {
          return lang;
        }
      }
    }

    LogInfo(string.Format("Fail to Parse TestLang. Input language:{0}", langType));

    return _defaultLang;
  }

  public string GetText(TestErrorCode error, TestLang lang)
  {
    //### Initialize cache data.
    string exMsg;
    if (!TryInitCache(out exMsg))
    {
      LogInfo(string.Format("Fail to Initialize Test cache data: {0} File path: {1} , CacheKey:{2}", exMsg, _resPath, _cacheKey));
      return Enums.GetEnumDesc(error);
    }

    //### Get Error text by Error code and Language type.
    string result;
    if (!_res.TryGetText(error, lang, _defaultLang, out result))
    {
      LogInfo(string.Format("Fail to Get Test message by ErrorCode {0} in {1} language. Result:{2}, Default language:{3}, File path: {4}", (int)error, lang.ToString(), result, _defaultLang.ToString(), _resPath));
    }

    return result;
  }

  private Test()
  {
  }

  private bool TryInitCache(out string exceptionMsg)
  {
    exceptionMsg = string.Empty;

    if (HttpRuntime.Cache[_cacheKey] == null)
    {
      try
      {
        HttpRuntime.Cache.Insert(
          _cacheKey,
          ReadResFile(),
          new CacheDependency(_resPath));

        _res = HttpRuntime.Cache[_cacheKey] as LangSer;

      }
      catch (Exception ex)
      {
        exceptionMsg = ex.Message;
        return false;
      }
    }

    return (_res != null && _res.HasData()) ? true : false;
  }

  private LangSer ReadResFile()
  {
    XmlDocument doc = new XmlDocument();

    try
    {
      doc.Load(_resPath);
    }
    catch
    {
      throw;
    }

    XmlNodeList msgList = doc.SelectNodes("/Test/MSG");
    if (msgList == null || msgList.Count == 0)
    {
      throw new Exception("<MSG> tag is void.");
    }

    LangSer res = new LangSer();
    res.Books = new Dictionary<string, LangSer.Book>();

    string[] langTypes = Enum.GetNames(typeof(TestLang));
    foreach (string lang in langTypes)
    {
      TryAdd(res.Books, lang, new LangSer.Book() { Errors = new Dictionary<string, string>() });
    }

    foreach (XmlNode msg in msgList)
    {
      //### Check MSG Code.
      XmlAttribute codeAttr = msg.Attributes["Code"];
      if (codeAttr == null
        || string.IsNullOrWhiteSpace(codeAttr.Value))
      {
        continue;
      }

      //### Check language types.
      string code = codeAttr.Value.Trim();
      foreach (string lang in langTypes)
      {
        XmlNode node = msg.SelectSingleNode(lang);
        if (node != null && !string.IsNullOrWhiteSpace(node.InnerText))
        {
          TryAdd(res.Books[lang].Errors, code, node.InnerText.Trim());
        }
      }
    }

    return res;
  }

  private void TryAdd<T, U>(Dictionary<T, U> dict, T key, U value)
  {
    U got;
    if (!dict.TryGetValue(key, out got))
    {   //If the key does not exist.
      dict.Add(key, value);
    }

    return;
  }

  private void LogInfo(string msg)
  {
    MethodBase method = MethodBase.GetCurrentMethod();
    _logger.Info(string.Format("[{0}][{1}] {2}", MethodBase.GetCurrentMethod().ReflectedType.FullName, MethodBase.GetCurrentMethod().Name, msg));
  }
}

 

 

Implementing the Singleton Pattern in C#

Implementing the Singleton Pattern in C#:

1. Using static constructor:

(1) BeforeFieldInit flag (default, initialize static members early):
Initialize static members => Main().

(2) Precise flag (using static constructor, initialize static members later):
Main() => Initialize static members => Static constructor.

Snipaste_2018-09-11_19-06-19.png

 

2. Sample code of Eager Singleton:


//### Eager Singleton:
//Precise flag: Main() => Initialize static _instance.

//Mark as sealed class to prevent someone from creating instances of derived types.
public sealed class Singleton
{
  //Creating instance the first time this class used.
  private static readonly Singleton _instance = new Singleton();

  private int _count = 0;

  // BeforeFieldInit flag:
  //  Initialize static members => Main().
  //*Precise flag: 
  //  Main() => Initialize static members => Static constructor.
  static Singleton()
  {
  }

  //Private and parameterless constructor will prevent other classes from instantiating it by public constructor.
  private Singleton()
  {
  }

  public static Singleton Instance
  {
    get
    {
      return _instance;
    }
  }

  public int GetNumber()
  {
    _count++;
    return _count;
  }
}

 

3. Sample code of Lazy Singleton (.NET Framework v4.0 or above):


//### Lazy Singleton:

//Mark as sealed class to prevent someone from creating instances of derived types.
public sealed class Singleton
{
  private static readonly Lazy<Singleton> _instanceHolder = new Lazy<Singleton>(() => new Singleton());

  private int _count = 0;

  //Private and parameterless constructor will prevent other classes from instantiating this by public constructor.
  private Singleton()
  {
  }

  public static Singleton Instance 
  {
    get 
    {
      return _instanceHolder.Value;
    }
  }

  public int GetNumber()
  {
    _count++;
    return _count;
  }
}

 

Reference:

1. C#單例模式的實現和性能對比 https://www.jianshu.com/p/3ae1bd656c1f
2. Implementing the Singleton Pattern in C# http://csharpindepth.com/Articles/General/Singleton.aspx
3. C# and beforefieldinit http://csharpindepth.com/Articles/General/BeforeFieldInit.aspx
4. What is so bad about singletons? https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons

 

In-memory cache dependent on an XML file

In-memory cache dependent on an XML file (for a single server):

Cache: 減少 Database Server 壓力, 減少網路流量, 增快速度, 提昇穩定.

1. 適於 Cache 之資料:

a) 全域資料,
b) 不常變動,
c) 資料量小,
d) 常用之資料.

 

2. Sample XML file:


<?xml version="1.0" encoding="utf-8" ?>
<Table1>
<Data>
<ID>1</ID>
<Code>Code_1</Code>
<Value>Value_1</Value>
</Data>
<Data>
<ID>2</ID>
<Code>Code_2</Code>
<Value>Value_2</Value>
</Data>
<Data>
<ID>3</ID>
<Code>Code_3</Code>
<Value>Value_3</Value>
</Data>
</Table1>

 

3. Sample code 1 (using HttpRuntime.Cache):


using System.Web;

public class CacheUtil
{
  public static string CacheKey_1
  {
    get
    {
      return ConfigurationManager.AppSettings["CacheKey_1"] ?? string.Empty;
    }
  }

  public static string GetData(string cacheKey)
  {
    if (string.IsNullOrEmpty(cacheKey))
    {
      return null;
    }

    //### Check if the specific data exists in memory cache.
    if (HttpRuntime.Cache[cacheKey] == null)
    {
      //### If the cache data does not exist, initialize the cache data.
      InitializeCache();
    }

    //### Get the specific data from memory cache.
    return HttpRuntime.Cache[cacheKey] as string;
  }

  private static void InitializeCache()
  {
    string cacheFilePath = string.Format("{0}/{1}",
                  AppDomain.CurrentDomain.BaseDirectory,
                  ConfigurationManager.AppSettings["cacheFilePath"]);

    //### Read data from the XML file.
    var dataSet = new DataSet();
    dataSet.ReadXml(cacheFilePath);

    //The CacheDependency class monitors the dependency relationships
    //so that when any of them changes, the cached item will be automatically removed.
    var dependency = new Caching.CacheDependency(cacheFilePath);

    foreach (DataRow row in dataSet.Tables[0].Rows)
    {
      //row[0]: Cache key.
      if (HttpRuntime.Cache[row[0]] == null)
      {
        HttpRuntime.Cache.Insert(
          row[0].ToString(),  //Cache key.
          string.Format("{0}-{1}", row[1], row[2]),  //Cache value.
          dependency);  //Dependencies.
      }
    }
  }
}

 

4. Sample code 2:


private static TestConfig GetTestConfig(string cacheKey, string configPath, out string rereadConfigXml)
{
  rereadConfigXml = string.Empty;

  if (HttpRuntime.Cache[cacheKey] == null)
  {
    try
    {
      HttpRuntime.Cache.Insert(
        cacheKey,
        ReadTestConfigFile(configPath, out rereadConfigXml),
        new CacheDependency(configPath));
    }
    catch
    {
      throw;
    }
  }

  return HttpRuntime.Cache[cacheKey] as TestConfig;
}

private static TestConfig ReadTestConfigFile(string xmlFilePath, out string configXml)
{
  configXml = string.Empty;
  TestConfig model = null;

  try
  {
    configXml = XDocument.Load(xmlFilePath).ToString();
    configXml = string.IsNullOrWhiteSpace(configXml) ? string.Empty : configXml.Trim();
    if (string.IsNullOrEmpty(configXml))
    {
      return null;
    }

    using (StringReader reader = new StringReader(configXml))
    {
      XmlSerializer serializer = new XmlSerializer(typeof(TestConfig));
      model = (TestConfig)serializer.Deserialize(reader);
    }
  }
  catch
  {
    throw;
  }

  return model;
}

 

5. Sample code 3:


public sealed class Test
{
  private readonly string _configPath = AppDomain.CurrentDomain.BaseDirectory + "Settings/TestConfig.xml";
  private readonly bool _defaultStatus = true;
  private readonly string _cacheKey = "TestCache";
  private static readonly Lazy<Test> _instanceHolder = new Lazy<Test>(() => new Test());
  private TestConfig _singletonData = null;

  public static Test Instance
  {
    get
    {
      return _instanceHolder.Value;
    }
  }

  public TestResponse Initialize()
  {
    TestResponse resp = new TestResponse()
    {
      RtnCode = (int)TestRtnCode.Fail,
    };

    if (HttpRuntime.Cache[_cacheKey] == null)
    {
      try
      {
        HttpRuntime.Cache.Insert(_cacheKey, ReadConfig(), new CacheDependency(_configPath));
      }
      catch (Exception ex)
      {
        resp.RtnMsg = string.Format("Exception:{0} - {1}", ex.Message, ex.StackTrace);
        return resp;
      }

      _singletonData = HttpRuntime.Cache[_cacheKey] as TestConfig;
    }

    resp.RtnCode = (int)TestRtnCode.Success;
    return resp;
  }

  public TestResponse CheckSomething(string param)
  {
    TestResponse resp = new TestResponse()
    {
      RtnCode = (int)TestRtnCode.Fail,
      SomeStatus = _defaultStatus
    };

    //......

    resp.RtnCode = (int)TestRtnCode.Success;
    return resp;
  }

  private Test()
  {
  }

  private TestConfig ReadConfig()
  {
    XmlDocument doc = new XmlDocument();

    try
    {
      doc.Load(_configPath);
    }
    catch
    {
      throw;
    }

    XmlNodeList nodeList = doc.SelectNodes("/TestConfig/XXX/YYY");
    if (nodeList == null || nodeList.Count == 0)
    {
      throw new Exception("<YYY> config is invalid.");
    }

    TestConfig rtnObj = new TestConfig();
    foreach (XmlNode node in nodeList)
    {
      XmlNode nodeID = node.SelectSingleNode("ZZZ");
      if (nodeID == null || string.IsNullOrWhiteSpace(nodeID.InnerText))
      {
        throw new Exception("<ZZZ> tag is void.");
      }
      //......
      //TryAdd(rtnObj.XXX, xxx.ID, xxx);
    }
    return rtnObj;
  }

  private void TryAdd<T>(Dictionary<string, T> dictionary, string key, T value)
  {
    T got;
    if (dictionary.TryGetValue(key, out got))
    {   //If the key already exists.
      return;
    }

    dictionary.Add(key, value);
  }

}

public class TestResponse
{
  public int RtnCode { get; set; }
  public string RtnMsg { get; set; }
  public bool SomeStatus { get; set; }
}

public class TestConfig
{
  //......
}

public enum TestRtnCode
{
  Success = 1,
  Fail = 0
}

 

Reference:

1. ASP.NET 4 快取 API 有兩種:Cache 與 ObjectCache https://blog.miniasp.com/post/2010/05/01/ASPNET-4-Cache-API-and-ObjectCache.aspx
2. MemoryCache with regions support? https://stackoverflow.com/questions/9003656/memorycache-with-regions-support