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. }

Converting a Sharepoint Personal View into a Public View List.

I have the problem where a user has created a view and I need to get access to it and make it public.  I appreciate this is quite a slog, especially in an ideal world the user would make the List public but if he/she has left …

The first problem was identifying the information for the view. After quite a bit of digging I find it in the WebParts table in the appropriate Content Database

Health and Safety warning – do not play with this data, there are some rather large and ugly dragons who are not scared to bite you, which in turn could incur the wrath of you superiors.

The field you are after is tp_view this contains a pseudo xml file showing the view.

To progress further I created a winform application with a textbox and a button, the string from tp_view goes in to the textbox. The button_click performs the magic.

We are going to use the splist.Views.Add function

Code Snippet
  1. SPView view = list.Views.Add("newView", strCollViewFields, strQuery, uint.Parse(RowLimit), true, false);

All of the information above is found in the string.

strCollViewFields is populated using, which is relatively straight forward the +13 means we get the end tag as well

Code Snippet
  1. private StringCollection GetViewFields()
  2.         {
  3.             XmlDocument xmlDoc = new XmlDocument();
  4.  
  5.             //assumes Viewfields is at the beginning of the string.
  6.             string xml = definition.Substring(0, definition.IndexOf("</ViewFields>") + 13);
  7.             xmlDoc.LoadXml(xml);
  8.  
  9.             var returnvar = new StringCollection();
  10.  
  11.             foreach (XmlNode node in xmlDoc.SelectNodes("ViewFields/FieldRef"))
  12.             {
  13.                 returnvar.Add(node.Attributes[0].Value);
  14.             }
  15.  
  16.             return returnvar;
  17.         }

strQuery, here I +7 is on the start tag because I do not want the Query tag, this is a SharePoint thing and I do not know why!

Code Snippet
  1. private string GetQuery()
  2.         {
  3.             string returnVar = string.Empty;
  4.  
  5.             int queryTagStartLocation = definition.IndexOf("<query>", StringComparison.InvariantCultureIgnoreCase) + 7;
  6.             int queryTagEndLocation = definition.IndexOf("</query>", StringComparison.InvariantCultureIgnoreCase);
  7.             int queryLength = queryTagEndLocation - queryTagStartLocation;
  8.             //All we need is the contents not the actual query tag
  9.             returnVar = definition.Substring(queryTagStartLocation, queryLength);
  10.  
  11.             return returnVar;
  12.         }

RowLimit  and Paged ( I have set it to true above)

Code Snippet
  1. private string GetRowLimit(out bool Paged)
  2.     {
  3.         //<RowLimit Paged="TRUE">100</RowLimit>
  4.         
  5.         string value = string.Empty;
  6.         string paged = string.Empty;
  7.        
  8.         XmlDocument xmlDoc = new XmlDocument();
  9.         xmlDoc.LoadXml(GetXMLString("RowLimit"));
  10.  
  11.         foreach (XmlNode node in xmlDoc.SelectNodes("RowLimit"))
  12.         {
  13.             paged = node.Attributes[0].Value;
  14.             value = node.InnerXml;
  15.         }
  16.  
  17.         Paged = bool.Parse(paged);
  18.  
  19.         return value;
  20.     }

Here I have created a new function GetXMLString() which I am sure you could parameterise and deprecate GetQuery()

Code Snippet
  1. private string GetXMLString(string tag )
  2. {
  3.    string startTag = "<" + tag ;
  4.    string endTag = "</" + tag + ">";
  5.  
  6.  
  7.    int queryTagStartLocation = definition.IndexOf(startTag, StringComparison.InvariantCultureIgnoreCase);
  8.    int queryTagEndLocation = definition.IndexOf(endTag, StringComparison.InvariantCultureIgnoreCase) + endTag.Length;
  9.     int queryLength = queryTagEndLocation - queryTagStartLocation;
  10.     //All we need is the contents not the actual query tag
  11.     return definition.Substring(queryTagStartLocation, queryLength);
  12. }

The List I had also has some aggregations which we can get from GetXMLString() function. 

All in all the main function looks like this

Code Snippet
  1. try
  2.             {
  3.                 definition = textBox1.Text;
  4.  
  5.                 bool paged;
  6.                 var strCollViewFields = GetViewFields();
  7.                 var strQuery = GetQuery();
  8.                 var RowLimit = GetRowLimit(out paged);
  9.                 var aggregations = GetXMLString("Aggregations"); ;
  10.  
  11.                 SPSecurity.RunWithElevatedPrivileges(delegate()
  12.                 {
  13.  
  14.                     using (var site = new SPSite("http://srvJupiter:6000/"))
  15.                     {
  16.                         using (var web = site.AllWebs[0])
  17.                         {
  18.  
  19.                             var list = web.Lists["Projects"];
  20.  
  21.                             SPView view = list.Views.Add("newView", strCollViewFields, strQuery, uint.Parse(RowLimit), true, false);
  22.  
  23.                             view.Aggregations = aggregations;
  24.                             view.Update();
  25.  
  26.                         }
  27.                     }
  28.                 }
  29.                 );

As usual test anything in a test system first.

Tuesday, 20 April 2010

Dundas Charts – Accessing Primary Key Information for Drill Down

I had the situation where I want to show Countries along the x (horizontal) axis, but for the drill down on the click event I wanted to use the Id for the item. The solution is surprisingly simple although did cause some head scratching for an hour or so.

We are going to store the additional information in an additional Y axis. To do so we need to tell the chart we are going to have an additional Y Value. The example below is for a Line chart, for other charts this might be different.

Code Snippet
  1. Chart.Series["Series"].YValuesPerPoint = 2;

Then I am binding using Points.DataBind to bind the data.  I have a dataset (items) with Destination which is my Country Name, DestinationId it’s primary key then PercentageErrorRate is my point on the graph. 

Code Snippet
  1. PercentErrorRateVTargetbyRAREgion.Series["ErrorRate"].YValuesPerPoint = 2;
  2. PercentErrorRateVTargetbyRAREgion.Series["ErrorRate"].Points.DataBind(items, "Destination", "PercentErrorRate,DestinationId", "");

On the y axis we are defining "PercentErrorRate,DestinationId" this list is comma delimited the first is the point shown on the graph then the rest can be accessed separately.

I am accessing this information on a click event, and have the following function.

Code Snippet
  1. public static void SetAttributes(Chart chart, int year, string month, string countries)
  2. {
  3.  
  4.     foreach (Series series in chart.Series)
  5.     {
  6.         //I know this could be in one string but this is easier to read.
  7.         var onclick = "Onclick=\"javascript:DundasHandler('" + chart.ID + "','#VALY2','" + year + "','" + month + "','" + countries + "','#SERIESNAME');\"";
  8.         var onMouseOver = "OnMouseOver=\"javascript:DundasHandIn('" + chart.ID + "', 'pointer'); \"";
  9.         var onMouseOut = "OnMouseOut=\"javascript:DundasHandOut('" + chart.ID + "'); \"";
  10.  
  11.         series.MapAreaAttributes = onclick + onMouseOut + onMouseOver;
  12.     }
  13. }

The bit we are interested in is #VALY2 this tells Dundas to insert the y2 value here, in this case DestinationId. I then have a javascript function which opens a new window and builds the URL.  I am using Telerik controls which is why I have radopen.

Code Snippet
  1. function DundasHandler(chartName, point, year, month, country, seriesName) {
  2.     var ownd = radopen("GridDialog.aspx?chartName=" + chartName + '&point=' + point + '&month=' + month + '&year=' + year + '&country=' + country + '&point2=' + seriesName, "RadWindow1");
  3. }

There you go  I hope it helps.