Saturday, May 7, 2016

Yesterday I realized that most events, summoned from one locale in code, may just be written as closures.

Consider these two methods from here:

  1. public ActionResult Export()
    {
       var gridViewSettings = new GridViewSettings();
       gridViewSettings.Name = "whatever";
       gridViewSettings.KeyFieldName = "CustomerID";
       gridViewSettings.Columns.Add("ContactName");
       gridViewSettings.Columns.Add("CompanyName");
       gridViewSettings.Columns.Add("ContactTitle");
       gridViewSettings.Columns.Add("City");
       gridViewSettings.Columns.Add("Phone");
       var printable = GridViewExtension.CreatePrintableObject(gridViewSettings,
             NorthwindDataProvider.GetCustomers());
       
       PrintingSystem ps = new PrintingSystem();
       
       using (this.headerImage = Image.FromFile(Server.MapPath("~\\Content\\pic.png")))
       {
          Link header = new Link();
          header.CreateDetailArea += new
                CreateAreaEventHandler(header_CreateDetailArea);
          
          PrintableComponentLink link1 = new PrintableComponentLink(ps);
          link1.Component = printable;
          
          CompositeLink compositeLink = new CompositeLink(ps);
          compositeLink.Links.AddRange(new object[] { header, link1 });
          
          compositeLink.CreateDocument();
          using (MemoryStream stream = new MemoryStream())
          {
             compositeLink.PrintingSystem.ExportToXls(stream);
             WriteToResponse("filename", true, "xls", stream);
          }
          ps.Dispose();
       }
       return PartialView("View");
    }
  2. void header_CreateDetailArea(object sender, CreateAreaEventArgs e)
    {
       e.Graph.BorderWidth = 0;

       Rectangle r = new Rectangle(0, 0, headerImage.Width, headerImage.Height);
       e.Graph.DrawImage(headerImage, r);

       r = new Rectangle(0, headerImage.Height, 400, 50);
       e.Graph.DrawString("Additional Header information here....", r);
    }

 
 

They may be hammered into one method like so:

public ActionResult Export()
{
   var gridViewSettings = new GridViewSettings();
   gridViewSettings.Name = "whatever";
   gridViewSettings.KeyFieldName = "CustomerID";
   gridViewSettings.Columns.Add("ContactName");
   gridViewSettings.Columns.Add("CompanyName");
   gridViewSettings.Columns.Add("ContactTitle");
   gridViewSettings.Columns.Add("City");
   gridViewSettings.Columns.Add("Phone");
   var printable = GridViewExtension.CreatePrintableObject(gridViewSettings,
         NorthwindDataProvider.GetCustomers());
   
   PrintingSystem ps = new PrintingSystem();
   
   using (this.headerImage = Image.FromFile(Server.MapPath("~\\Content\\pic.png")))
   {
      Link header = new Link();
      header.CreateDetailArea += new
            CreateAreaEventHandler(
(object sender, CreateAreaEventArgs e) => {
               e.Graph.BorderWidth = 0;
            
               Rectangle r = new Rectangle(0, 0, headerImage.Width, headerImage.Height);
               e.Graph.DrawImage(headerImage, r);
            
               r = new Rectangle(0, headerImage.Height, 400, 50);
               e.Graph.DrawString("Additional Header information here....", r);
            }
);
      
      PrintableComponentLink link1 = new PrintableComponentLink(ps);
      link1.Component = printable;
      
      CompositeLink compositeLink = new CompositeLink(ps);
      compositeLink.Links.AddRange(new object[] { header, link1 });
      
      compositeLink.CreateDocument();
      using (MemoryStream stream = new MemoryStream())
      {
         compositeLink.PrintingSystem.ExportToXls(stream);
         WriteToResponse("filename", true, "xls", stream);
      }
      ps.Dispose();
   }
   return PartialView("View");
}

 
 

A followup refactoring after this might be to get rid of headerImage as a class wide variable and just have it isolated to the Export method. That seems cleaner to me.

Friday, May 6, 2016

How can I jam a header onto an Excel sheet I poop out from DevExpress grid data in an MVC app?

This way of doing things needs love if we are going to have a header. In fact, the approach radically changes. I have had an example working, but it yet needs spruce up. The code I offer below is really dirty code but I've been sitting on this for a few days I and I wanted to share it instead of maybe getting to it later. Most of it is stolen from elsewhere, but I don't recall where.

using System.Drawing;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using DevExpress.Web.Mvc;
using DevExpress.XtraPrinting;
using DevExpress.XtraPrintingLinks;
using Modernity.Models;
namespace Modernity.Controllers
{
   public class ExportingController : Controller
   {
      private System.Drawing.Image headerImage;
      
      public ActionResult Export()
      {
         var gridViewSettings = new GridViewSettings();
         gridViewSettings.Name = "whatever";
         gridViewSettings.KeyFieldName = "CustomerID";
         gridViewSettings.Columns.Add("ContactName");
         gridViewSettings.Columns.Add("CompanyName");
         gridViewSettings.Columns.Add("ContactTitle");
         gridViewSettings.Columns.Add("City");
         gridViewSettings.Columns.Add("Phone");
         var printable = GridViewExtension.CreatePrintableObject(gridViewSettings,
               NorthwindDataProvider.GetCustomers());
         
         PrintingSystem ps = new PrintingSystem();
         
         using (this.headerImage = Image.FromFile(Server.MapPath("~\\Content\\pic.png")))
         {
            Link header = new Link();
            header.CreateDetailArea += new
                  CreateAreaEventHandler(header_CreateDetailArea);
            
            PrintableComponentLink link1 = new PrintableComponentLink(ps);
            link1.Component = printable;
            
            CompositeLink compositeLink = new CompositeLink(ps);
            compositeLink.Links.AddRange(new object[] { header, link1 });
            
            compositeLink.CreateDocument();
            using (MemoryStream stream = new MemoryStream())
            {
               compositeLink.PrintingSystem.ExportToXls(stream);
               WriteToResponse("filename", true, "xls", stream);
            }
            ps.Dispose();
         }
         return PartialView("View");
      }
      
      void WriteToResponse(string fileName, bool saveAsFile, string fileFormat,
            MemoryStream stream)
      {
         string disposition = saveAsFile ? "attachment" : "inline";
         Response.Clear();
         Response.Buffer = false;
         Response.AppendHeader("Content-Type", string.Format("application/{0}",
               fileFormat));
         Response.AppendHeader("Content-Transfer-Encoding", "binary");
         Response.AppendHeader("Content-Disposition", string.Format("{0}; filename={1}.
               {2}", disposition, fileName, fileFormat));
         Response.BinaryWrite(stream.GetBuffer());
         Response.End();
      }
      
      void header_CreateDetailArea(object sender, CreateAreaEventArgs e)
      {
         e.Graph.BorderWidth = 0;
      
         Rectangle r = new Rectangle(0, 0, headerImage.Width, headerImage.Height);
         e.Graph.DrawImage(headerImage, r);
      
         r = new Rectangle(0, headerImage.Height, 400, 50);
         e.Graph.DrawString("Additional Header information here....", r);
      }
   }
}

When your load balancer is stripping the s off of https in what it presents to you...

You can't just get dependable URLs from an app for "itself" (links back to itself in emails sent and the like) like so:

Request.Url.ToString().ToLower().Split('?')[0].Replace("/yourimmediatething.aspx","");

 
 

The base URL is going to need to be a setting somewhere in the Web.config or the database or something. Don't try to just replace http:// universal with https:// as you'll find this makes it impossible to test in dev. It's time to stop being clever and start hacking.

Thursday, May 5, 2016

When doing an INSERT in T-SQL you cannot insert specifying numeric positions of columns instead of column names.

Apparently there is a way to do an insert specifying nothing at all assuming you are to use every column in order. Just leave out the first set of parenthesis. This suggests an example might be:

INSERT INTO INVOICE VALUES( 1,1,'KEYBOARD',1,15,5,75);

Wednesday, May 4, 2016

Make Excel sheets exported from DevExpress MVC grids hide and rearrange columns based upon how the user hides and rearranges columns at the grid itself.

more settings here... columns are defined, etc. in black copy at the link's blog posting is a place where one fills in columns at a grid. These now need to be hydrated like so:

settings.KeyFieldName = "CustomerID";
foreach (string column in GridViewPartialViewColumns.Get())
{
   settings.Columns.Add(column);
}
settings.ClientLayout = (sender, e) =>
{
   if (e.LayoutMode == ClientLayoutMode.Saving)
   {
      ReportExporter.BackingStore = (MVCxGridView)sender;
   }
};

 
 

We need to pull the master list of columns in default order from a second place. Let's do something like so:

using System.Collections.Generic;
namespace MoreModernModernity.Views.Home
{
   public static class GridViewPartialViewColumns
   {
      public static List<string> Get()
      {
         return new List<string>()
         {
            "ContactName",
            "CompanyName",
            "ContactTitle",
            "City",
            "Phone"
         };
      }
   }
}

 
 

The ReportExporter class I offer here need to be significantly overhauled.

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using DevExpress.Web.Mvc;
using DevExpress.XtraPrinting;
using Modernity.Views.Home;
namespace Modernity.Models
{
   public static class ReportExporter
   {
      public static MVCxGridView BackingStore { get; set; }
      
      private static GridViewSettings PrepareSettings()
      {
         int counter = 0;
         Dictionary<int, Tuple<int, bool>> instructions = new Dictionary<int, Tuple<int,
               bool>>();
         while (counter < BackingStore.Columns.Count)
         {         
            instructions.Add(BackingStore.Columns[counter].VisibleIndex,new
                  Tuple<int,bool>(counter,BackingStore.Columns[counter].Visible));
            counter++;
         }
         var settings = new GridViewSettings();
         settings.Name = "grid";
         settings.KeyFieldName = BackingStore.KeyFieldName;
         foreach (string column in Rearrange(GridViewPartialViewColumns.Get(),
               instructions))
         {
            settings.Columns.Add(column);
         }
         return settings;
      }
      
      private static List<string> Rearrange(List<string> defaultColumns, Dictionary<int,
            Tuple<int, bool>> instructions)
      {
         List<string> newColumns = new List<string>();
         int counter = 0;
         foreach (string defaultColumn in defaultColumns)
         {
            if (instructions[counter].Item2)
            {
               newColumns.Add(defaultColumns[instructions[counter].Item1]);
            }
            counter++;
         }
         return newColumns;
      }
      
      public static ActionResult MakeExcelSheet(Object data)
      {
         GridViewSettings settings = PrepareSettings();
         ActionResult actionResult = GridViewExtension.ExportToXls(settings, data,
               new XlsExportOptionsEx { ExportType =
               DevExpress.Export.ExportType.WYSIWYG });
         return actionResult;
      }
   }
}

 
 

This could be more elegant. BackingStore could be a dictionary of MVCxGridView types that one could look up by magic string names such as "Home" which would allow thus to scale beyond one implemenation. I guess it needs love in other places too if that is to work. GridViewPartialViewColumns needs some sort of inheritance implementation, etc.

Tuesday, May 3, 2016

I saw some flakiness in which a DevExpress namespace could show up and the appropriate .dll was in the bin folder yet the project wasn't referenincg the .dll!

It happened with DevExpress.XtraPrinting.v15.2.dll and I had to manually add the .dll as a reference to get the code to understand the PrintingSystem type even though the namespace was looped in OK. Messed up! You can't just type in some nonsense in a using declaration and expect C# to compile, you have to use a namespace that really is a namespace, but how does C# know of a namespace outside of the references? Is it crawling the bin folder and using what it finds to halfway work in a misleading way? WTF? Maybe a different DevExpress .dll depends on DevExpress.XtraPrinting.v15.2.dll which was why it was in the bin folder to begin with (as I didn't put it there) and... You know what, I bet a namespace is split across two .dlls and I had the ineffective half to start with. Maybe that is what is going on. Nasty.

Monday, May 2, 2016

How do I allow users to export GridViews to Excel sheets in DevExpress' MVC paradigm?

The example here is pretty convoluted and even includes a few methods which are not used, but it basically suggests that you'd have a button in your view like so:

@using (Html.BeginForm("Index", "Exporting"))
{
   <input type="submit" value="Export" />
}

 
 

Assuming NorthwindDataProvider.GetCustomers() is what hydrates our grid lets go ahead and use that to also hydrate our Excel sheet. The Excel sheet does not need to read directly from the GridView. This is going to hit an action like so:

using System;
using System.Web.Mvc;
using Modernity.Models;
namespace Modernity.Controllers
{
   public class ExportingController : Controller
   {
      public ActionResult Index()
      {
         Object data = NorthwindDataProvider.GetCustomers();
         return ReportExporter.MakeExcelSheet(data);
      }
   }
}

 
 

My helper class for returning the ActionResult is below. GridViewSettings is a DevExpress thing found in the DevExpress.Web.Mvc namespace.

using System;
using System.Web.Mvc;
using DevExpress.Web.Mvc;
using DevExpress.XtraPrinting;
namespace Modernity.Models
{
   public static class ReportExporter
   {
      public static ActionResult MakeExcelSheet(Object data)
      {
         GridViewSettings gridViewSettings = new GridViewSettings();
         gridViewSettings.Name = "report";
         ActionResult actionResult = GridViewExtension.ExportToXls(gridViewSettings,
               data, new XlsExportOptionsEx {ExportType =
               DevExpress.Export.ExportType.WYSIWYG});
         return actionResult;
      }
   }
}

 
 

One may make an Adobe Acrobat PDF instead of an Excel sheet by making the second the last line of code immediately above look like so:

ActionResult actionResult = GridViewExtension.ExportToPdf(gridViewSettings, data);