NLog tutorial

Hi, folks!

Today I am going to show you how to set logging of your project with minimum effort using NLog. NLog is a simple to use logging platform with good documentation, tutorials and examples and also available source code. You can install package using NuGet selecting NLog.Config, which will also install it’s dependencies (NLog and NLog.Schema). Installation adds NLog.dll to our project refrences, NLog.config for configuration and NLog.xsd (which won’t be needed in our case).

Initial configuration is simple and allows basic internal logging. But our goal is to set new log file each day with date in it’s name and email notifications in case of warnings and (fatal) errors.

<variable name="logFilePath" value="Logs\NLog.${shortdate}.log" />

Inside of variable tag we declared log file path with short date in it’s name. With date in it’s name new log file will be created every day. After that we have to create a file target and mail target:

<targets>
  <target name="logfile" 
          xsi:type="File"
          fileName="${logFilePath}"
          layout="${longdate}   LEVEL=${level:upperCase=true}: ${message}${newline} (${stacktrace}) ${exception:format=tostring}"
          keepFileOpen="true" />

  <target name="mail" 
          xsi:type="Mail"
          smtpServer="my.smtp.server.com"
          smtpPort="port"
          smtpUserName="user.name"
          smtpPassword="password"
          subject="${machinename} - My subject string (${shortdate:format=dd. MM. yyyy})"
          from="address.to.send.from@mail.com"
          to="address.to.send.to@mail.com"
          layout="${longdate}   LEVEL=${uppercase:${level}},   LOCATION=${callsite:className=true:includeSourcePath=true:methodName=true},               
                  MESSAGE=${message}${newline} EXCEPTION=${exception:format=tostring,StackTrace}${newline}" />
</targets>

Now we have two targets. First named “logfile” will add new entries to our log. Eeach entry will consist from long date, level and message. After message there will be new line with stacktrace and exception message. Second named “mail” will send email to address.to.send.to@mail.com. Entry will be quite similar as previous with added location (class name and method name). But we have a problem – each log entry will be sent separately! If we want to send all entries in one single email, we have to wrap our “mail” target with target of type BufferingWrapper:

<target name="mailbuffer" xsi:type="BufferingWrapper" slidingTimeout="false" bufferSize="100" flushTimeout="-1">
  <target name="mail" 
          xsi:type="Mail"
          smtpServer="my.smtp.server.com"
          smtpPort="port"
          smtpUserName="user.name"
          smtpPassword="password"
          subject="${machinename} - My subject string (${shortdate:format=dd. MM. yyyy})"
          from="address.to.send.from@mail.com"
          to="address.to.send.to@mail.com"
          layout="${longdate}   LEVEL=${uppercase:${level}},   LOCATION=${callsite:className=true:includeSourcePath=true:methodName=true},               
                  MESSAGE=${message}${newline} EXCEPTION=${exception:format=tostring,StackTrace}${newline}" />
</target>

Problem solved. 🙂 Now we will get all entries inside of a single email. After that we have to set rules, which will tell NLog which level of entries must be written to which target.

<rules>
  <logger name="*" minlevel="Info" writeTo="logfile" />
  <logger name="*" minlevel="Warn" writeTo="mailbuffer"/>
</rules>

With those rules two rules we told NLog to log to file everything with level equal or higher to Info and to send an email of everything with level equal or higher to warning. Out logger is now configured. After that we have to add namespace to each class we want to log (using NLog;). We also have to initialize our logger. After that we can start to create logs:

class Program
{
  private static Logger logger = LogManager.GetCurrentClassLogger();

  static void Main(string[] args)
  {
    logger.Info("This is the first line of Main method.");

    try
    {
      throw new Exception("This is one badass exception. :)");
    }
    catch (Exception ex)
    {
      logger.Error(ex, "It seems the exception happened. :(");
    }

    logger.Warn("This is your last warning!");
    logger.Fatal("And this is fatal error...");
  }
}

Here you can download config file and short code snippet: NLog config and short code snippet. Before using you have to configure SMTP parameters (you can find all possible parameters in documentation or you can check this example (both on GitHub)).

How to export collection of data to Xlsx file in C# using ClosedXML

ClosedXML is a fine library for exporting data to Excel. It is very simple to use and fast enough for smaller sets of data. But it also has some cons:

1.) It uses OpenXML – that is not a con by itself, but the problem appears when you want to save file as Xls. XML concept of Office files was presented with Office 2007 and is not compatible with previous versions of Office. But if you really need Xls, you can create Excel file using ClosedXML and than open and save it as Xls using Interop. That is only possible if you are exporting data on client side with installed PIA and Office. It is strongly advised against using Interop on server side due to the fact that Office is client side application!
2.) It is very slow for big datasets (more than few 10 thousand rows). For exporting big datasets you should use OpenXML (maybe I will talk about that in one of my next posts).
3.) On project’s CodePlex site it is written (quote): “ClosedXML makes it easier for developers to create Excel 2007/2010 files.”. But I believe Excel 2013 and newer should also open files generated with ClosedXML (haven’t tested it though).

Firstly you have to add reference to ClosedXML. You can do that using NuGet Package Manager or you can download ClosedXML DLL from CodePlex and add a reference to your project.

NuGet

NuGet Package Manager

You also need to add a namespace to your CS file (using ClosedXML.Excel;).

Using ClosedXML you can set specific cell values, you can insert data from DataTable or from some other collection. In this post I will show you how to create generic function, which inserts data to Excel from IEnumerable of objects. I will pass column titles in separate List of string arrays.


Info: I could probably set column titles using descriptors on object properties – something like this:

[MyAttribute("First property")]
public string FstProperty
{
  get {....}
  set {....}
}

But for the sake of the example I won’t complicate – I will pass the titles to the function in a separate List.


Our function should look something like this:

public byte[] ExportToExcel<T>(IEnumerable<T> data, string worksheetTitle, List<string[]> titles)
{  
  var wb = new XLWorkbook(); //create workbook
  var ws = wb.Worksheets.Add(worksheetTitle); //add worksheet to workbook

  ws.Cell(1, 1).InsertData(titles); //insert titles to first row
            
  if (data != null && data.Count() > 0)
  {
    //insert data to from second row on
    ws.Cell(2, 1).InsertData(data); 
  }

  //save file to memory stream and return it as byte array
  using (var ms = new MemoryStream())
  {
    wb.SaveAs(ms);

    return ms.ToArray();
  }
}

And that is it! If we wish to add some special styling to our heading row, we have to modify our code snippet a little:

var rangeTitle = ws.Cell(1, 1).InsertData(titles); //insert titles to first row
rangeTitle.AddToNamed("Titles");

var titlesStyle = wb.Style;
titlesStyle.Font.Bold = true; //font must be bold
titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //align text to center
titlesStyle.Fill.BackgroundColor = XLColor.Red; //background must be red

wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; //attach style to the range

We can also adjust column width to maximum content width using function AdjustToContents right before we save the file:

ws.Columns().AdjustToContents();

Push C# (Razor) objects to JavaScript array of objects

If you wonder, how to push C# objects to a JavaScript array in ASP.NET View using Razor, you came to the right place. 🙂 In this post I will tell you, how I achieved it.

Lets say I have a collection List<Category>. Object Category consists of parameters id, category name and list of subcategories (also of type Category):

public class Category {
  public int Id { get; set; }

  public string Name { get; set; }

  public List<Category> Subcategories { get; set; }
}

List<Category> is also my strongly typed View’s Model. My goal is to achieve Javascript array like this: [{“id”:…, “name”:”…”, “subcategories”:[{“id”:…, “name”:”…”}]},{…}]. In order to create objects like that, I must firstly create collection of anonymous objects and serialize it to string.

@{
  //serializer initialization
  System.Web.Script.JavascriptSerializer js = new System.Web.Script.JavascriptSerializer();

  var data = Model.Select(c => new {
    id = c.Id,
    name = c.Name,
      subcategories = c.Subcategories.Select(cc => new {
      id = cc.Id,
      name = cc.Name
    })
  });

  //serialize collection of anonymous objects
  string arrStr = js.Serialize(data);
}

Now I have to inject this string to JavaScript:

var categories = JSON.parse('@arrStr'.replace(/&quot;/g, '"'));

As you can see, I injected string using Razor. I also used replace function in order to replace all &quot; occurances with " (double quotes).

Warning: JavaScript replace function replaces only first occurrance of searched pattern. In order to replace all occurrances I had to use regular expression with flag g, which is used to perform a global match.

The last thing to do is to call JSON.parse, which converts properly processed string into JavaScript object. Now we have JavaScript array of desired objects, which can be used further in our JavaScript functions/events.

How to override IIS default error messages?

Today I was working on retrieving status codes in ASP.NET MVC 4 application. I needed correct status codes for my ajax requests to show correct error message (in case of an error, of course). The problem was that IIS was redirecting when status code was added to response. In case of status code 401 it redirected to basic login, in case of status code 403 it redirected to IIS generic error page and so on. If you are working with .NET version 4.5 there exists very simple solution:

Response.TrySkipIisCustomErrors = true;

But not with .NET version 4.0. 🙂 In that case you have to add few lines into your Web.config file inside of system.webServer node:

<httpErrors existingResponse="PassThrough">
  <remove statusCode="403"/>
  <error statusCode="403" responseMode="ExecuteURL" path="~/StatusCode/Unauthorized"/>
  <remove statusCode="401"/>
  <error statusCode="401" responseMode="ExecuteURL" path="~/StatusCode/Forbidden"/>
  <remove statusCode="404"/>
  <error statusCode="404" responseMode="ExecuteURL" path="~/StatusCode/NotFound"/>
</httpErrors>

With attribute value “PassThrough” (attribute existingResponse) you tell IIS to leave response untouched if an existing response exists. You can check other attribute options here. Value of attribute path inside of node error represents the path to the response (for example: “~/ControllerName/ActionName”). That way you can override IIS’ default error responses with your custom error responses.

How to get user names and surnames and show them in ASP.NET MVC 4 autocomplete?

Today I was working on some autocompletion with users (name + surname) from Active Directory (from now on AD) in ASP.NET MVC 4 application. I wanted to get suggestions on the fly based on user’s input. I was accessing AD using Lightweight Directory Access Protocol (from now on LDAP).

You have to add two references into your project: System.DirectoryServices.dll and System.DirectoryServices.AccountManagement.dll. In your class you have to use two namespaces:

using System.DirectoryServices.AccountManagement;
using System.DirectoryServices;
In your controller you do something like this:
public JsonResult AutocompleteUsers(string term)
{
    string filter = "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))(|(givenName={0}*)(sn={1}*)))";
    List persons = new List();

    DirectoryEntry dE = new DirectoryEntry(); //initialization
    {
        //uniquely identifies entry in a networked environment
        dE.Path = "directory entry path"; 

        //username for client authentication
        dE.Username = "directory entry username"; 
        
        //password for client authentication
        dE.Password = "directory entry password";

        //authentication types
        dE.AuthenticationType = AuthenticationTypes.ServerBind; 
    }
    
    DirectorySearcher dS = new DirectorySearcher(dE); //applying search filter
    {
        dS.Filter = String.Format(filter, term, term);
    }

    foreach (SearchResult result in dS.FindAll())
    {
        if (result != null)
        {
            DirectoryEntry entry = result.GetDirectoryEntry();
            
            string name = (entry.Properties["givenName"].Value ?? "").ToString();
            string surname = (entry.Properties["sn"].Value ?? "").ToString();

            if (!String.IsNullOrEmpty(name) && !String.IsNullOrEmpty(surname)) 
            {
                persons.Add(name + " " + surname);
            }
        }
    }

    IEnumerable ipersons = persons.AsEnumerable().OrderBy(p => p);

    return this.Json(ipersons, JsonRequestBehavior.AllowGet);
}
So, firstly you initialize connection with AD. After that you apply search filter. Part (objectCategory=person) returns user and contact objects. Because we only want users, we also need part (!userAccountControl:1.2.840.113556.1.4.803:=2). Contacts don’t have userAccountControl attribute, so this part will always be True for them. With part (objectClass=user) we restrict query to just user object. Part (|(givenName={0}*)(sn={1}*))) restricts query on users which name or surname starts with specified string. After that we loop through search results and get the names.
Collection of names is returned as Json. We must allow get with option JsonRequestBehavior.AllowGet. By default “Json get” is not allowed and without mentioned option exception is produced (Internal Server Error).On client site we have to attach following JavaScript code:

$(document).ready(function () {
    $('#textbox_id').autocomplete({
	source: '@Url.Action("action_name", "controller_name")'
    });
});
Textbox value (inserted string) is automaticaly sent in query string as parameter term. We must not forget to import jQuery scripts:
@Styles.Render("~/Content/themes/base/css")
@Scripts.Render("~/bundles/jquery")    
@Scripts.Render("~/bundles/jqueryui")

Without that callback won’t happen. 🙂