Thursday, June 9, 2011

Creating Awesome Logging Control with NLog, WPF, C# and Memory Target

First we need to define new "reactive" memory target. This target will notify us when some log item is received in memory.

Code Snippet
  1. using System;
  2. using NLog;
  3. using NLog.Targets;
  4.  
  5. namespace VWBT.Controls.Log
  6. {
  7.     public class MemoryEventTarget : Target
  8.     {
  9.         public event Action<LogEventInfo> EventReceived;
  10.  
  11.         /// <summary>
  12.         /// Notifies listeners about new event
  13.         /// </summary>
  14.         /// <param name="logEvent">The logging event.</param>
  15.         protected override void Write(LogEventInfo logEvent)
  16.         {
  17.             if (EventReceived != null) {
  18.                 EventReceived(logEvent);
  19.             }
  20.         }
  21.     }
  22. }

Now we are ready to define our logging control. We will keep last 50 log messages in the ObservableColection, which we will bind to the ListView control. We also register event for our memory target inwhich we update our collection.



Code Snippet
  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Windows.Controls;
  4. using NLog;
  5. using VWBT.Controls.Log;
  6.  
  7. namespace VWBT.Controls
  8. {
  9.     /// <summary>
  10.     /// Interaction logic for LoggingControl.xaml
  11.     /// </summary>
  12.     public partial class LoggingControl : UserControl
  13.     {
  14.         readonly MemoryEventTarget _logTarget;  // My new custom Target (code is attached here MemoryQueue.cs)
  15.  
  16.         public static ObservableCollection<LogEventInfo> LogCollection { get; set; }
  17.  
  18.  
  19.         public LoggingControl()
  20.         {
  21.             LogCollection = new ObservableCollection<LogEventInfo>();
  22.  
  23.             InitializeComponent();
  24.  
  25.             // init memory queue
  26.             _logTarget = new MemoryEventTarget();
  27.             _logTarget.EventReceived += EventReceived;
  28.             NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(_logTarget, LogLevel.Debug);
  29.         }
  30.  
  31.         private void EventReceived(LogEventInfo message)
  32.         {
  33.             Dispatcher.Invoke(new Action(() => {
  34.                 if (LogCollection.Count >= 50) LogCollection.RemoveAt(LogCollection.Count - 1);
  35.                 LogCollection.Add(message);
  36.             }));
  37.         }
  38.     }
  39. }


Following is the simple design of the logging control. Please note the binding.


Code Snippet
  1. <UserControl x:Class="VWBT.Controls.LoggingControl"
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:Log="clr-namespace:VWBT.Controls.Log" mc:Ignorable="d"
  6.              d:DesignHeight="230" d:DesignWidth="457"
  7.              DataContext="{Binding RelativeSource={RelativeSource Self}}">
  8.     <UserControl.Resources>
  9.         <Log:LogItemBgColorConverter x:Key="LogItemBgColorConverter" />
  10.         <Log:LogItemFgColorConverter x:Key="LogItemFgColorConverter" />
  11.     </UserControl.Resources>
  12.     <Grid>
  13.         <!--<TextBox IsReadOnly="True" AcceptsReturn="True"  Height="Auto" HorizontalAlignment="Stretch" Name="dgLog" VerticalAlignment="Stretch" Width="Auto"/>-->
  14.         <ListView ItemsSource="{Binding LogCollection}" Name="logView">
  15.             <ListView.ItemContainerStyle>
  16.                 <Style TargetType="{x:Type ListViewItem}">
  17.                     <Setter Property="ToolTip" Value="{Binding FormattedMessage}" />
  18.                     <Setter Property="Background" Value="{Binding Level, Converter={StaticResource LogItemBgColorConverter}}" />
  19.                     <Setter Property="Foreground" Value="{Binding Level, Converter={StaticResource LogItemFgColorConverter}}" />
  20.                     <Style.Triggers>
  21.                         <Trigger Property="IsSelected" Value="True">
  22.                             <Setter Property="Background" Value="DarkOrange"/>
  23.                             <Setter Property="Foreground" Value="black"/>
  24.                         </Trigger>
  25.                         <Trigger Property="IsMouseOver" Value="True">
  26.                             <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Background}"/>
  27.                             <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Foreground}"/>
  28.                         </Trigger>
  29.                     </Style.Triggers>
  30.                 </Style>
  31.             </ListView.ItemContainerStyle>
  32.             <ListView.View>
  33.                 <GridView>
  34.                     <GridView.Columns>
  35.                         <!--<GridViewColumn DisplayMemberBinding="{Binding LoggerName}" Header="Logger"/>-->
  36.                         <GridViewColumn DisplayMemberBinding="{Binding Level}" Header="Level"/>
  37.                         <GridViewColumn DisplayMemberBinding="{Binding FormattedMessage}" Width="500" Header="Message"/>
  38.                         <GridViewColumn DisplayMemberBinding="{Binding Exception}" Header="Exception"/>
  39.                     </GridView.Columns>
  40.                 </GridView>
  41.             </ListView.View>
  42.         </ListView>
  43.         <!--<ListBox Height="Auto" HorizontalAlignment="Stretch" Name="dgLog" VerticalAlignment="Stretch" Width="Auto" />-->
  44.     </Grid>
  45. </UserControl>


Previous XAML code uses couple converters which allow us to display messages in different color.


Code Snippet
  1. using System;
  2. using System.Globalization;
  3. using System.Windows.Data;
  4. using System.Windows.Media;
  5.  
  6. namespace VWBT.Controls.Log
  7. {
  8.     public class LogItemBgColorConverter : IValueConverter
  9.     {
  10.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  11.         {
  12.             if ("Warn" == value.ToString()) {
  13.                 return Brushes.Yellow;
  14.             } else if ("Error" == value.ToString()) {
  15.                 return Brushes.Tomato;
  16.             }
  17.             return Brushes.White;
  18.         }
  19.  
  20.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  21.         {
  22.             throw new NotImplementedException();
  23.         }
  24.     }
  25. }


Code Snippet
  1. using System;
  2. using System.Globalization;
  3. using System.Windows.Data;
  4. using System.Windows.Media;
  5.  
  6. namespace VWBT.Controls.Log
  7. {
  8.     public class LogItemFgColorConverter : IValueConverter
  9.     {
  10.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  11.         {
  12.             if ("Error" == value.ToString()) {
  13.                 return Brushes.Black;
  14.             }
  15.             return Brushes.Black;
  16.         }
  17.  
  18.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  19.         {
  20.             throw new NotImplementedException();
  21.         }
  22.     }
  23. }

That's all folks. With this control you are ready to display your logs directly in your application! Comments welcome!

Numeric and Boolean Expression Parser Using Conversion From Infix to Postfix using C#

Dears

Below is a source code to the expression parser with ability to parse combined expressions, that is numeric and booleans, and variable support. See Main method for demo purposes.


Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. public class ExpressionEvaluator
  5. {
  6.     #region class AtomicExpression
  7.     /// <summary>
  8.     /// This class holds atomic expressions. It holds either variables, values or operators.
  9.     /// If the class holds operator, it can provide result given the input values
  10.     /// </summary>
  11.     public class AtomicExpression
  12.     {
  13.         private string _value;
  14.  
  15.         /// <summary>
  16.         /// Parsed value of the strig value used during evaluation
  17.         /// </summary>
  18.         public object ParsedValue { get; set; }
  19.  
  20.         /// <summary>
  21.         /// Value of the current expression
  22.         /// It is either numeric value or name of the variable (e.g. 1, A, Aleph)
  23.         /// </summary>
  24.         public string Value
  25.         {
  26.             get
  27.             {
  28.                 return _value;
  29.             }
  30.             set
  31.             {
  32.                 double outd;
  33.                 if (double.TryParse(value, out outd)) {
  34.                     ParsedValue = outd;
  35.                 }
  36.                 bool outb;
  37.                 if (bool.TryParse(value, out outb)) {
  38.                     ParsedValue = outb;
  39.                 }
  40.                 _value = value;
  41.             }
  42.         }
  43.         /// <summary>
  44.         /// String representation of the operator (e.g. '==' or '||')
  45.         /// </summary>
  46.         public string Operator { get; set; }
  47.  
  48.         /// <summary>
  49.         /// Evaluates the unary operator with provided value
  50.         /// </summary>
  51.         /// <param name="value">Value for unary operator</param>
  52.         /// <returns>Evaluated expression of the unary operator</returns>
  53.         public object Evaluate(object value)
  54.         {
  55.             if (Operator == "!") {
  56.                 return !((bool)value);
  57.             }
  58.             throw new NotImplementedException(Operator + " operator not implemented");
  59.         }
  60.  
  61.         /// <summary>
  62.         /// Evaluates the binary operator expression with provided values
  63.         /// </summary>
  64.         /// <param name="val1">Left-side value for the operator</param>
  65.         /// <param name="val2">Right-side value for the operator</param>
  66.         /// <returns>Output value of the binary expression</returns>
  67.         public object Evaluate(object val1, object val2)
  68.         {
  69.             Console.Write("{0} {1} {2} = ", val1, Operator, val2);
  70.             switch (Operator) {
  71.                 case "+":
  72.                     return (double)val1 + (double)val2;
  73.                 case "-":
  74.                     return (double)val1 - (double)val2;
  75.                 case "*":
  76.                     return (double)val1 * (double)val2;
  77.                 case "/":
  78.                     return (double)val1 / (double)val2;
  79.                 case ">":
  80.                     return (double)val1 > (double)val2;
  81.                 case ">=":
  82.                     return (double)val1 >= (double)val2;
  83.                 case "<":
  84.                     return (double)val1 < (double)val2;
  85.                 case "<=":
  86.                     return (double)val1 <= (double)val2;
  87.                 case "==":
  88.                     return (double)val1 == (double)val2;
  89.                 case "!=":
  90.                     return (double)val1 != (double)val2;
  91.                 case "&&":
  92.                     return (bool)val1 && (bool)val2;
  93.                 case "||":
  94.                     return (bool)val1 || (bool)val2;
  95.  
  96.             }
  97.             throw new NotImplementedException(Operator + " operator not implemented");
  98.         }
  99.  
  100.         public override string ToString()
  101.         {
  102.             return string.Format("{0}{1}", Value, Operator);
  103.         }
  104.     }
  105.     #endregion
  106.  
  107.     #region Evaluate(List<AtomicExpression> list, Dictionary<string, object> variables)
  108.     /// <summary>
  109.     /// Evaluates the input AtomicExpression (see AtomicExpression) list with the list of provided variables
  110.     /// and calculates the numeric or boolean output. List of expressions is sorted in infix order.
  111.     /// </summary>
  112.     /// <param name="list">List of input expressions. Expressions are evaluated in the postfix manner.</param>
  113.     /// <param name="variables">List of variables and their values used during the evaluation</param>
  114.     /// <returns></returns>
  115.     public object Evaluate(List<AtomicExpression> list, Dictionary<string, object> variables)
  116.     {
  117.         var stack = new Stack<object>();
  118.  
  119.         foreach (var expression in list) {
  120.             // double value
  121.             if (expression.ParsedValue != null) {
  122.                 stack.Push(expression.ParsedValue);
  123.                 continue;
  124.             }
  125.             // variable
  126.             if (!string.IsNullOrEmpty(expression.Value)) {
  127.                 stack.Push(variables[expression.Value]);
  128.                 continue;
  129.             }
  130.             // unary operator
  131.             object result;
  132.             if (expression.Operator == "!") {
  133.                 result = expression.Evaluate(stack.Pop());
  134.                 Console.WriteLine(result);
  135.             }
  136.                 // binary operator
  137.             else {
  138.                 var p2 = stack.Pop();
  139.                 var p1 = stack.Pop();
  140.                 result = expression.Evaluate(p1, p2);
  141.                 Console.WriteLine(result);
  142.             }
  143.             stack.Push(result);
  144.         }
  145.         Console.WriteLine("Result: " + stack.Peek());
  146.         return stack.Pop();
  147.     }
  148.     #endregion
  149.  
  150.     #region CheckParenthesis(string expression)
  151.     /// <summary>
  152.     /// Checks if given expression is well formed with parenthesis
  153.     /// </summary>
  154.     /// <param name="expression">Input expression (e.g. A+B-(2*2)>0)</param>
  155.     /// <returns></returns>
  156.     public virtual bool CheckParenthesis(string expression)
  157.     {
  158.         var stack = new Stack<string>();
  159.  
  160.         foreach (var c in expression) {
  161.             switch (c) {
  162.                 case '(':
  163.                     stack.Push(c.ToString());
  164.                     break;
  165.                 case ')':
  166.                     if (stack.Count == 0) {
  167.                         return false;
  168.                     }
  169.                     stack.Pop();
  170.                     break;
  171.             }
  172.         }
  173.         return stack.Count <= 0;
  174.     }
  175.     #endregion
  176.  
  177.     #region List<AtomicExpression> Postfix(string expression)
  178.     /// <summary>
  179.     /// Converts the input infix expression into the postfix and creates the list
  180.     /// of atomic expressions in this infix order
  181.     /// </summary>
  182.     /// <param name="expression">Infix expression (e.g. A+B-(2*2)>0||B)</param>
  183.     /// <returns>List of atomic expressions sorted in the infix order</returns>
  184.     public virtual List<AtomicExpression> Postfix(string expression)
  185.     {
  186.         var expressions = new List<AtomicExpression>();
  187.         var variable = string.Empty;
  188.         var stack = new Stack<string>();
  189.         // remove spaces
  190.         expression = expression.Replace(" ", string.Empty);
  191.         char c;
  192.         string sc;
  193.  
  194.         for (var i = 0; i < expression.Length; i++) {
  195.             c = expression[i];
  196.  
  197.             // variable or number
  198.             if (char.IsLetterOrDigit(c)) {
  199.                 variable += c;
  200.             }
  201.                 // left parthesis
  202.             else if (c == '(') {
  203.                 stack.Push(c.ToString());
  204.             }
  205.                 // right parenthesis
  206.             else if (c == ')') {
  207.                 // add variable
  208.                 if (!string.IsNullOrEmpty(variable)) {
  209.                     expressions.Add(new AtomicExpression { Value = variable });
  210.                 }
  211.                 variable = string.Empty;
  212.                 while (!stack.Peek().Equals("(")) {
  213.                     expressions.Add(new AtomicExpression { Operator = stack.Pop() });
  214.                 }
  215.                 stack.Pop();
  216.             }
  217.                 // operator
  218.             else {
  219.                 sc = c.ToString();
  220.  
  221.                 // add variable
  222.                 if (!string.IsNullOrEmpty(variable)) {
  223.                     expressions.Add(new AtomicExpression { Value = variable });
  224.                 }
  225.                 variable = string.Empty;
  226.  
  227.                 // check for operators having 2 characters
  228.                 if (i < expression.Length - 1 && !char.IsLetterOrDigit(expression[i + 1]) && expression[i + 1] != '(' && expression[i + 1] != ')') {
  229.                     sc += expression[i + 1];
  230.                     i++;
  231.                 }
  232.                 // add all expressions with higher or same priority
  233.                 while (stack.Count > 0 && Priority(stack.Peek()) >= Priority(sc)) {
  234.                     expressions.Add(new AtomicExpression { Operator = stack.Pop() });
  235.                 }
  236.                 stack.Push(sc);
  237.             }
  238.         }
  239.         while (stack.Count > 0) {
  240.             expressions.Add(new AtomicExpression { Operator = stack.Pop() });
  241.         }
  242.         return expressions;
  243.     }
  244.     #endregion
  245.  
  246.  
  247.     // main method
  248.  
  249.     #region Main(string[] args)
  250.     public static void Main(string[] args)
  251.     {
  252.         ExpressionEvaluator s = new ExpressionEvaluator();
  253.         var list = s.Postfix("A && !(B > 2 + 1) || ( !A && !(B==2))");
  254.  
  255.         Console.WriteLine("------------------------------------------");
  256.         foreach (var expression in list) {
  257.             Console.Write(expression.ToString());
  258.         }
  259.         Console.WriteLine("\r\n------------------------------------------");
  260.         var dict = new Dictionary<string, object>();
  261.         dict.Add("A", true);
  262.         dict.Add("B", 3d);
  263.         s.Evaluate(list, dict);
  264.         Console.ReadKey();
  265.     }
  266.     #endregion
  267.  
  268.     // private methods
  269.  
  270.     #region Priority(string x)
  271.     private static int Priority(string x)
  272.     {
  273.         if (x.Equals("||"))
  274.             return 0;
  275.         if (x.Equals("&&"))
  276.             return 1;
  277.         if (x[0].Equals('<') || x[0].Equals('>') || x[0].Equals('=') || x[0].Equals('!'))
  278.             return 2;
  279.         if (x.Equals("+") || x.Equals("-"))
  280.             return 3;
  281.         if (x.Equals("*") || x.Equals("/"))
  282.             return 4;
  283.         if (x.Equals("(") || x.Equals(")"))
  284.             return -1;
  285.         throw new NotImplementedException(x + " is not implemented");
  286.     }
  287.     #endregion
  288. }

Wednesday, January 16, 2008

Javascript: How to prevent double clicks on submit button

This solution uses timers, so in accidental clicks which do not lead into postback it allows to resubmit after period is over



/* This disables Double clicks on submit buttons */
var currentClick;
var timerSet;

function ClearClick()
{
currentClick = '';
timerSet = false;
}

function DC(id)
{
var retVal = !(id == currentClick);
currentClick = id;

if (!timerSet)
{
setTimeout('ClearClick()',10000);
timerSet = true;
}
return retVal;
}

Tuesday, January 15, 2008

C# Templated Control with Design Support

I just finished an implementation of general templated control. I'll add more explanations later.



// Set an attribute reference to the designer, and define
// the HTML markup that the toolbox will write into the source.
[Designer(typeof(TemplateGroupsSampleDesigner)),
ToolboxData("<{0}:TemplateGroupsSample runat=server></{0}:TemplateGroupsSample>")]
public class TemplatedControl : WebControl, INamingContainer
{
// Field for the templates

#region Variables
private ITemplate template;
private Panel pan;
#endregion

// Ctor

#region TemplatedControl()
public TemplatedControl()
{
}
#endregion

// For each template property, set the designer attributes
// so the property does not appear in the property grid, but
// changes to the template are persisted in the control.

#region BodyTemplate
[Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate BodyTemplate
{
get { return template; }
set { template = value; }
}
#endregion

#region CreateChildControls()
protected override void CreateChildControls()
{
// Instantiate each template inside a panel
// then add the panel to the Controls collection

pan = new Panel();
template.InstantiateIn(pan);
this.Controls.Add(pan);
}
#endregion

//#region RenderBeginTag(HtmlTextWriter writer)
///// <summary>
///// This part is rendered before the template content
///// </summary>
///// <param name="writer"></param>
//public override void RenderBeginTag(HtmlTextWriter writer)
//{
// //base.Render(writer);
// writer.AddAttribute("style", "border: solid 5px red");
// writer.RenderBeginTag("div");

//}
//#endregion

//#region RenderEndTag(HtmlTextWriter writer)
///// <summary>
///// This part is rendered after template content
///// </summary>
///// <param name="writer"></param>
//public override void RenderEndTag(HtmlTextWriter writer)
//{
// writer.RenderEndTag();
//}
//#endregion
}

#region class TemplateGroupsSampleDesigner
// Designer for the TemplateGroupsSample control
public class TemplateGroupsSampleDesigner : ControlDesigner
{
// Variables

#region Variables
TemplateGroupCollection col = null;
#endregion

// Properties

#region TemplateGroups
public override TemplateGroupCollection TemplateGroups
{
get
{

if (col == null)
{
// Get the base collection
col = base.TemplateGroups;

// Create variables
TemplateGroup tempGroup;
TemplateDefinition tempDef;
TemplatedControl ctl;

// Get reference to the component as TemplateGroupsSample
ctl = (TemplatedControl)Component;

// Create a TemplateGroup
tempGroup = new TemplateGroup("Main Template");

// Create a TemplateDefinition
tempDef = new TemplateDefinition(this, "Body Template",
ctl, "BodyTemplate", true);

// Add the TemplateDefinition to the TemplateGroup
tempGroup.AddTemplateDefinition(tempDef);


// Add the TemplateGroup to the TemplateGroupCollection
col.Add(tempGroup);
}

return col;
}
}
#endregion

#region AllowResize
// Do not allow direct resizing unless in TemplateMode
public override bool AllowResize
{
get
{
if (this.InTemplateMode)
return true;
else
return false;
}
}
#endregion

// Public methods

#region Initialize(IComponent component)
public override void Initialize(IComponent component)
{
// Initialize the base
base.Initialize(component);
// Turn on template editing
SetViewFlags(ViewFlags.TemplateEditing, true);
}
#endregion

#region GetDesignTimeHtml()
// Add instructions to the placeholder view of the control
public override string GetDesignTimeHtml()
{
return CreatePlaceHolderDesignTimeHtml("Click here and use " +
"the task menu to edit the templates.");
}
#endregion
}
#endregion

C# Password Generator

Simple, yet very effective password generator which i use for new users registration.


public static string Generate(int passwordLength)
{
string _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789_";

Byte[] randomBytes = new Byte[passwordLength];
Random rGen = new Random();
rGen.NextBytes(randomBytes);

char[] chars = new char[passwordLength];
int allowedCharCount = _allowedChars.Length;

for (int i = 0; i < passwordLength; i++)
{
chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount];
}

return new string(chars);
}