Wednesday, 4 March 2015

ASP.NET Generating Excel Exports using Data Annotations

In my ASP.NET MVC application, I have recently had the requirement to generate a few Excel Spreadsheets from Data.  I soon realised I had a lot of boiler plate code that I was starting to copy and paste all over the show.  

So I took the opportunity to sit back and see if there was a better solution. I use Data Annotations a lot (for example see my Tool tip Data Annotation blog),  so it seemed to make sense to configure the export here too.

For excel generation I use  epplus see http://epplus.codeplex.com/, there is a nuget package too.


The Data Annotation.

Data annotations are simple enough, for this one I decided I would like to have an Index for order, and to be able to override the name of the property.


public class ExportToXlsAttribute : DescriptionAttribute
   {
       public ExportToXlsAttribute()
           : base("")
       {
 
       }
 
 
       public int Index { getset; }
       public ExportToXlsAttribute(string description, int index)
           : base(description)
       {
           Index = index;
       }
   }

The Data Class.

this is how the data class could look, this could be a return object from either a view, stored procedure or a linq query


public partial class GetWidgetsForExport
 {
     public int WidgetId{ getset; }

     public int? WidgetTypeId { getset; }
 
     [ExportToXls(Index = 3)]
     [DisplayName("Widget Type")]
     public string WidgetTypeDescription { getset; }
 
     [ExportToXls("Part Number",2)]
     public string PartNumber { getset; }
 
     [ExportToXls(Index = 1)]
     public string Comment { getset; }
 
  
 }

Getting the Properties for Export

Firstly we need to get a list of all of the properties which are going to be in the spreadsheet.
A simple class is used to handle these, 
internal class ItemsForExport
{
    public string PropertyName { getset; }
    public int Index { getset; }
    public string Title { getset; }
}

These are pretty straightforward, PropertyName, the name of the property, Index this the place in the spreadsheet, and Title is the column header.

To populate this class we need to do some reflection. I have simplified the code for the sake of brevity, for example code can be added in the displayName setting based on the DataAnnotation DisplayName.

private List<ItemsForExport> GetPropertiesForExport(Type type)
{
    const BindingFlags allProperties = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
    var itemsToExport = new List<ItemsForExport>();
 
    foreach (var property in type.GetProperties(allProperties))
    {
        var exportToXlsAttributes = property.GetCustomAttributes(typeof (ExportToXlsAttribute)).ToList();
 
        if (exportToXlsAttributes.Any())
        {
            var displayName = property.Name;
            var attribute = (ExportToXlsAttribute) exportToXlsAttributes.First();
           
            if (!string.IsNullOrEmpty(attribute.Description))
            {
                displayName = attribute.Description;
            }
            itemsToExport.Add(new ItemsForExport {Order = attribute.Index, PropertyName = property.Name, Title = displayName});
        }
    }
 
    return itemsToExport.OrderBy(a => a.Order).ToList();
}

Again this is relatively straight forward, the code loops through the properties, looks to see if there is a DataAnnotation ExportToXlsAttribute if there is, it populates the class and adds to the list.  Finally it sends back the list in order.

Generating the Excel Spreadsheet.

We now have our properties we want, so simply we loop through our list of objects looking for the right items and add them to the spreadsheet.

               using (var p = new ExcelPackage())
               {
                   p.Workbook.Properties.Title = title;
 
                   //Create a sheet
                   p.Workbook.Worksheets.Add(title);
 
                   var sheet = p.Workbook.Worksheets[1];
 
 
                   var row = 1; // 1 is the header.
                   foreach (var item in listOfObjects)
                   {
                       var col = 1;
                       if (row == 1)
                       {
                           foreach (var itemForExport in itemsForExport)
                           {
                               sheet.Cells[1, col].Value = itemForExport.Title;
                               col++;
                           }
                           row++;
                           col = 1;
                       }
 
                       foreach (var itemForExport in itemsForExport)
                       {
                           var pi = item.GetType().GetProperty(itemForExport.PropertyName);
                           sheet.Cells[row, col].Value = pi.GetValue(item);
                           col++;
                       }
 
                       row++;
                   }
 
                   return p.GetAsByteArray();
               }

The byteArray which is returned can then be used by FileContentResult on the controller.


  [AcceptVerbs(HttpVerbs.Post)]
      public FileContentResult DownloadXls(string saveOption, string browser)
      {
          const string filename = "Export.xlsx";
 
          return File(BuildXlsFromDataAnnotation(), "text/csv", filename);
      }


This should be enough for you to get started, you could for example add more properties on the DataAnnotation to be able format the cells within the spreadsheet.

Update 1

If you have dates (and who doesn't?) in your export it is worth making this amendment. This will format dates to dd-mmm-yyyy format and tell excel that it is a date col.

var pi = item.GetType().GetProperty(itemForExport.PropertyName);
sheet.Cells[row, col].Value = pi.GetValue(item);
if (pi.PropertyType.FullName.Contains("DateTime"))
{
   sheet.Cells[row, col].Style.Numberformat.Format = "dd-MMM-yyyy";
}


Friday, 4 July 2014

Tooltip Data Annotation

I am a fan of Data Annotations, I like the fact I can define stuff in one place and it works everywhere.
One thing I felt that was missing is a tooltip annotation, which then using the technique discussed in “Standardising through HTML helpers

Coding the Data Annotation

The Data Annotation is going to look like this
   [StringLength(255)]
   [DisplayName("Colour")]
   [ToolTip("This is the colour used in the Project Management Page,
 please use html colours.")]
   public string PriorityColour { getset; }
To create the data annotation is simple and documented here, but this is the code.

 public class ToolTipAttribute : DescriptionAttribute

 {
     public ToolTipAttribute()
         : base("")
     {

     }

     public ToolTipAttribute(string description)
         : base(description)
     {

     }
 }

Adding the Data Annotation to myTextboxFor

Depending on your JS control set of choice you may need to amend this slightly. I am using Bootstrap.
The first step is getting the tooltip from the Data Annotation, to do this we are going to get all of the Member Expressions from the Model.
As I have a number of different myxxxFor, I do this in a separate function. which is relatively simple, it is a case of iterating through the MemberExpressions until you find the tooltip and then returning the string.
private static string GetToolTipFromAnnotation(MemberExpression memberExpression)
   {
       string toolTip = "";

       foreach (Attribute attribute in memberExpression.Expression.Type 
.GetProperty(memberExpression.Member.Name).GetCustomAttributes(false))
       {
           if (typeof(ToolTipAttribute== attribute.GetType())
           {
               toolTip = ((ToolTipAttribute)attribute).Description;
           }
       }

       return toolTip;
   }

Calling this code from the MyTextBoxFor code is
string toolTip = GetToolTipFromAnnotation((MemberExpression)expression.Body);

Then we need to add some additional classes to our input class, 
in the last blog we had MyTextboxFor returning this.
return helper.TextBoxFor(expression, formatString, new { @class = additionalClass});

To include the tooltip we need a bit more.
return helper.TextBoxFor(expression, formatString, new {@class = additionalClass 
" tooltipHolder"@data_toggle = "tooltip"@data_content = toolTip});

Then on your page call the popover (or tooltip, I use popover as 
I prefer the look and feel.)
$(".tooltipHolder").popover({ trigger'hover' });
As usual the code is examples and may need more or less work for you but 
I hope it helps.

Friday, 27 June 2014

Standardising through HTML Helpers

HTML TextBoxFor in Razor are really useful.


I know that is a bit of an understatement,but they can be even better.  I use bootstrap (www.getbootstrap.com) and a number of other js components such as the bootstrap date picker( http://www.eyecon.ro/bootstrap-datepicker/ ) .  And by creating my own versions of the helpers we can add a lot of conformity and standardisation.  Also if we want to change the control it is in a central place!



Creating our own helper.

The helper needs to be static and return an MvcHtmlString .

The new helper is going to be called MyTextBoxFor and will be called in the same way as the original helper, like so.

 @Html.MyTextBoxFor( model => model.Name)

For Bootstrap we need to include a class I use "form-control" normally using Razor, we would need to add a class element such as;

@Html.TextBoxFor(model => model.Name, new {@class = "form-control"}) 

so for our helper we are going to include this.


 public static MvcHtmlString MyTextBoxFor(this HtmlHelper helper, Expression> expression, bool addReadonly = false)
    { 
      var additionalClass = "form-control";
      var formatString = string.Empty;
      return helper.TextBoxFor(expression, formatString, new { @class = additionalClass});
    }

Already we are starting to save ourselves some effort, now we can get away with just calling the first snippet rather than the 2nd.

The astute of you will notice the formatString variable in the class, what is that for?  As mentioned earlier I use JS components, we can start to plug some of these in.

Expression> allows us to access information about the property we are building the control, in particular Body.Type gets us the data type.

the next step is to do a quick query on this and we can start to add some more information

this time we are going to add the datePicker class and a format String ( I like my dates to display like 25-Dec-2015)



  public static MvcHtmlString MyTextBoxFor(this HtmlHelper helper, Expression> expression, bool addReadonly = false)
    {
        string additionalClass = "form-control";
        

        // add datepicker class to date data types/
        Type dataType = expression.Body.Type;
        var formatString = string.Empty;

       
        if (dataType == typeof(DateTime) || dataType == typeof(DateTime?))
        {
            additionalClass = "datePicker form-control";
            formatString = "{0:dd-MMM-yyyy}";
        }
              
        return helper.TextBoxFor(expression, formatString, new { @class = additionalClass});
    }

And that is about all there is to it, there is obviously much more you can do, for example Read Only is almost plugged in. You could do spinners for numbers etc.

I hope this helps and enjoy.

Update 
I have just added a new blog which uses this technique to display tooltips please see here http://cursethecompiler.blogspot.co.uk/2014/07/tooltip-data-annotation.html 

Thursday, 27 June 2013

ASP.Net MVC View as PDF

So I needed to export a view as a pdf, and although the Chrome trick of printing to PDF is okay the page was too wide and needed tweaking.

After the usual google search I came up with a couple of things I thought might be useful and with a bit of patience came up with the following solution.

  1. Create a view of the item I want to print (along with the controller)
  2. Convert the html to string
  3. Use a tool based on WkHtmlToPdf to create the pdf. (Pechkin)

1.Create a view of the item I want to print

This is pretty straight forward, create a new view probably without a master layout. The only slight change is css has to be part of the view.
If you want to keep items together you can use 
 page-break-inside: avoid;

2.Convert the html to string

This uses the view engine to render the html with the data.
firstly we assign our model to the view
this.ViewData.Model = vm;

Then we need to do the magic

 using (StringWriter stringWriter = new StringWriter())
{
               ViewEngineResult viewResult = ViewEngines.Engines.FindView(this.ControllerContext, "Print", null);

               ViewContext viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, stringWriter);

                viewResult.View.Render(viewContext, stringWriter);

                var st = stringWriter.GetStringBuilder().ToString();
}
so we now have a string "st" with the html in it.

3. Export to PDF.
So to export to pdf I used a tool based on WkHtmlToPdf, the reason for this is it uses Webkit to render the page, before exporting to pdf.  It did a much better job of it than plain iTextSharp.

The tool I used is Pechkin which also has a nuget package Pechkin.Synchronized (which is also a winner :-) )

There are 2 config objects you can set. GlobalConfig and ObjectConfig


global config 

This sets the page size and also bookmarks etc.
 
           GlobalConfig gc = new GlobalConfig();
            // set it up using fluent notation because we can 
            gc.SetDocumentTitle("A Title ")
              .SetPaperSize(PaperKind.A4)
              .SetOutlineGeneration(true);


object config

This allows me to add colours to my background, a footer and set the font size
 
            ObjectConfig oc = new ObjectConfig();
            oc.SetPrintBackground(true);
            oc.Footer.SetFontSize(8);
            oc.Footer.SetLeftText(" Generated By:" + HttpContext.User.Identity.Name + " on " + DateTime.Now.ToString("dd-MMM-yyyy HH:mm"));

Generating the pdf

This generates the pdf, converts it to a byte array and post it back to the browser. We pass the global config in when we create the object, and object config when we create the document
 
var pechkin = new Pechkin.Synchronized.SynchronizedPechkin(gc);
return File(pechkin.Convert(oc,st), "application/pdf");
One of the nice things with this approach is you can use a "normal" controller to test the html and call the view like so (assuming your printable view is called "print"
 
 return View("print", vm);
The in javascript I called the controller in the normal way
 
  window.open("/controller/Print?id=" + id);
I hope this is of help.

Update

a couple of gotchas on deploy,
On IIS 7 I had to enable 32 bit application on the Application Pool

and needed a redirect on the common.logging dll in web.config

    
        
    





Tuesday, 25 May 2010

Accessing SharePoint List Data using Linq

Recently I wanted to return a SharePoint list as an List<> of a business object.

I did some scouring on the internet, as there is no point re-inventing the wheel.  But was generally disappointed with the results, as they required a lot of iterations and loops. Eric White’s blog here got me thinking, that we could do it with Linq

So here is what I did Projects my business object looks like this

Property Data Type
ProjectOwner string
KPI string
SKU string
DateRequired DateTime?
DueDate string
   

And here is the code, which expects a result set from a Sharepoint Web Service GetListItems() method.

Code Snippet
  1. private static List<ProjectStatus> GetProjectStatusesFromXmlNode(XmlNode resultNode)
  2.     {
  3.         var list = (from r in resultNode.GetXElement().Descendants()
  4.                     where r.Name.LocalName == "row"
  5.                     select new ProjectStatus
  6.                     {
  7.                         Project = (string)r.Attribute("ows_LinkTitle"),
  8.                         DateRequired = DateTime.Parse(((string)r.Attribute("ows_DateRequired"))),
  9.                         DueDate = (string)r.Attribute("ows_ECRDueDate"),
  10.                         SKU = (string)r.Attribute("ows_SKU"),
  11.                         Status = (string)r.Attribute("ows_Status"),
  12.                         ProjectOwner = (string)r.Attribute("ows_AssignedTo"),
  13.                         KPI = (string)r.Attribute("ows_tKPI")
  14.                     }
  15.                     ).ToList();
  16.         return list;
  17.     }

You will also need Eric White’s extension

Code Snippet
  1. public static class Extensions
  2. {
  3.     public static XElement GetXElement(this  XmlNode node)
  4.     {
  5.         XDocument xDoc = new XDocument();
  6.         using (XmlWriter xmlWriter = xDoc.CreateWriter())
  7.             node.WriteTo(xmlWriter);
  8.         return xDoc.Root;
  9.     }
  10.  
  11. }