API response caching

API response caching:


public class CacheBase
{
  protected int _expireDuration;
  protected long _cacheTimestamp;

  public int KeyMaxCount { get; set; }
  public dynamic SingleData { get; set; }
  public List<dynamic> ListOfData { get; set; }

  public CacheBase()
  {
    KeyMaxCount = 2000;
  }

  public bool IsExpire()
  {
    if (CurrentTimestamp() - _cacheTimestamp < _expireDuration)
    {
      return false;
    }

    return true;
  }

  protected long CurrentTimestamp()
  {
    return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
  }
}

 


public class Person
{
  public int ID { get; set; }
  public string Name { get; set; }
}

public class PersonCache : CacheBase
{
  public PersonCache(List<Person> dataList)
  {
    _expireDuration = 20;
    _cacheTimestamp = CurrentTimestamp();

    if (dataList != null)
    {
      DataList = dataList.Cast<dynamic>().ToList();
    }
  }
}

 


protected bool IsCacheExpire<U>(ConcurrentDictionary<string, U> cacheDict, string key, out U cache)
	where U : CacheBase
{
  cache = default(U);
  if (cacheDict.TryGetValue(key, out cache) && cache != null
    && !cache.IsExpire())
  {
    return false;
  }

  if (cache != null && cacheDict.Count > cache.KeyMaxCount)
  {
    CleanCache(cacheDict);
  }

  return true;
}

protected void CleanCache<U>(ConcurrentDictionary<string, U> cacheDict)
  where U : CacheBase
{
  if (cacheDict == null || cacheDict.Count == 0)
  {
    return;
  }

  NLog.Logger logger = NLog.LogManager.GetLogger("CleanCache");
  StringBuilder log = new StringBuilder();

  try
  {
    LogCache(cacheDict, ref log);

    U got;
    foreach (var cache in cacheDict)
    {
      if (cache.Value != null && cache.Value.IsExpire())
      {
        cacheDict.TryRemove(cache.Key, out got);
      }
    }

    log.AppendLine("[After clean]");
    LogCache(cacheDict, ref log);
    logger.Info(log.ToString());
  }
  catch (Exception ex)
  {
    logger.Error(ex, log.ToString());
  }
}

protected void LogCache<U>(ConcurrentDictionary<string, U> cacheDict, ref StringBuilder log)
{
  if (cacheDict == null || cacheDict.Count == 0)
  {
    return;
  }

  log.AppendFormat("Cache Count: {0}", cacheDict.Count).AppendLine();
  log.Append("Keys: ");
  foreach (var cache in cacheDict)
  {
    log.AppendFormat("{0},", cache.Key);
  }
  log.AppendLine();
}

protected void AddNewCache<T>(ConcurrentDictionary<int, T> cacheDict, int key, T data)
{  //To reset the Cache Timestamp.
  T cache;
  if (cacheDict.TryGetValue(key, out cache))
  {
    cacheDict.TryRemove(key, out cache);
  }
  cacheDict.TryAdd(key, data);
}

 


private static ConcurrentDictionary<int, PersonCache> _personCacheDict = new ConcurrentDictionary<int, PersonCache>();

public List<Person> GetPeople(int keys)
{

  #region Query from cache

  uncachedKeys = new List<int>();
  List<Person> rtn = new List<Person>();

  foreach (int key in keys)
  {
    PersonCache cache;
    if (!IsCacheExpire<PersonCache>(_personCacheDict, key, out cache) 
      && cache.DataList != null)
    {
      cache.DataList.ForEach(data => rtn.Add(data));
    }
    else
    {
      uncachedKeys.Add(key);
    }
  }

  #endregion

  #region Query from db

  List<Person> dbDataList = QueryFromDB(uncachedKeys);

  if (dbDataList.Count == 0)
  {
    return rtn;
  }

  #endregion

  #region Write into the cache

  foreach (int key in uncachedKeys)
  {
    List<Person> list = (from p in dbDataList where p.ID == key select p).ToList();
    AddNewCache(_personCacheDict, key, new PersonCache(list));
  }

  #endregion

  return rtn.AddRange(dbDataList);
}

 

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));
  }
}

 

 

Serialize / deserialize XML in C#:

Serialize / deserialize XML in C#:

1. Serialize object to XML string:


protected static string ToXMLString(this object obj)
{
  if (obj == null)
  {
    return null;
  }

  try
  {
    Using (StringWriter writer = new StringWriter())
    {
      XmlSerializer serializer = new XmlSerializer(obj.GetType());
      serializer.Serialize(writer, obj);
      return writer.ToString();
    }
  }
  catch
  {
    return null;
  }
}

 

2. Deserialize XML string to object:

2.1 Specific class:


protected Test FromXML(string xml)
{
  if (string.IsNullOrEmpty(xml))
  {
    return null;
  }

  Test model = null;

  try
  {
    using (StringReader reader = new StringReader(xml))
    {
      XmlSerializer serializer = new XmlSerializer(typeof(Test));
      model = (Test)serializer.Deserialize(reader);
    }
  }
  catch
  {
    throw;
  }

  return model;
}

 

2.2 Generic class:


protected T FromXML<T>(string xml)
{
  if (string.IsNullOrEmpty(xml))
  {
    return default(T);
  }

  T model = default(T);

  try
  {
    using (StringReader reader = new StringReader(xml))
    {
      XmlSerializer serializer = new XmlSerializer(typeof(T));
      model = (T)serializer.Deserialize(reader);
    }
  }
  catch
  {
    throw;
  }

  return model;
}

 

2.3 Compare:

Snipaste_2018-09-07_19-03-59

 

3. Deserialize XML string from file to object:


protected Test ReadXmlFile(string xmlFilePath, out string xml)
{
  xml = string.Empty;
  Test model = null;

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

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

  return model;
}

 

4. Faster way to deserialize xml string:


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);
}

 

Reference:

Convert XML String to Object https://stackoverflow.com/questions/3187444/convert-xml-string-to-object