Monday, 15 June 2009

Dynamically generated Visual Studio Reports

Although I was able to get reports generated using the ReportViewer I soon found that I needed the layout of these reports to be dynamically generated whether they were for a single record showing each required field on its own line or a group of associated records in a table format (but again showing only the required fields).

XSL/XSLT

I soon across Dan Shipe's blog C# Shooter with both C# and XSLT code to help the dynamic generation of reports, however this was geared towards web generated reports (WebForms) and I needed it for Windows Application (WinForms).
Then I came across an answer of StackFlow by MusiGenesis (which referred to Dan's blog) and this proved to be what I needed. The code was provided me with the basis of what I needed (but still using Dan's XSL file).
From this I was able to start dynamically generating reports.

Passing simple Parameters to XSL

Although the Dataset was being Transformed with the XSL file I needed other data to be added.
I found somewhere about the XsltArgumentList class which would allow for the adding of parameters which would then be passed through the Transform process

xform.Transform(xmlDomSchema, xslArgs, writer);

Passing Array Parameters to XSL

After many attempts I figured out that the passing of an array was unlikely through the normal method and fortunately found Oleg Tkachenko's answer on the Bytes.com website.
Here Oleg detailed the requirement 'to build XmlDocument or XPathDocument, then ask it to create XPathNavigator, select nodelist of values and pass it.'
I soon implemented his code and after many attempts I began to understand what was happening and soon tweaked it so I could it a Dictionary list (thus giving me the key name of the field and the value of the column width - which would be summed and then ratioed and appended with 'cm').

public partial class frmReport_General_groups : Form
{
private DataSet ds;
private XsltArgumentList xslArgs;
private const decimal REPORT_WIDTH = 17.75m;

public frmReport_General_groups(DataSet ds, int customerID, string customerName,
string reportName, Dictionary<string,int> columnWidth)
{
InitializeComponent();
this.ds = ds;
reportName += "s";
this.Text += reportName + " report : " + customerName +
" (" + customerID.ToString().PadLeft(6, '0') + ")";
xslArgs = new XsltArgumentList();
xslArgs.AddParam("reportWidth", "", REPORT_WIDTH.ToString()+"cm");
xslArgs.AddParam("reportName", "", reportName);
xslArgs.AddParam("customerName", "", customerName);
xslArgs.AddParam("customerID", "", customerID);

//create XPathNavigator, select nodelist of values and pass it.
StringWriter sw = new StringWriter();
XmlTextWriter w = new XmlTextWriter(sw);
w.WriteStartElement("root");
int totalWidth = 0;
foreach (KeyValuePair<string,> pair in columnWidth)
{
totalWidth += pair.Value;
}
foreach (KeyValuePair<string,int> pair in columnWidth)
{
w.WriteElementString(pair.Key,
(REPORT_WIDTH * ((decimal)pair.Value / (decimal)totalWidth)).ToString("n2") +
"cm");
}
w.WriteEndElement();
w.Close();
//XmlNodeWriter should be used instead of temporary string
XPathDocument doc = new XPathDocument(new StringReader(sw.ToString()));
XPathNavigator navColumns = doc.CreateNavigator();
xslArgs.AddParam("navColumns", "", navColumns.Select("/root/*"));
}

private void frmReport_General_groups_Load(object sender, EventArgs e)
{
string AppPath = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);

Stream rdlc = RdlcEngine.BuildRDLCStream(
ds, "general_groups", AppPath + "\\general_groups.xslt", xslArgs);

reportViewer1.LocalReport.LoadReportDefinition(rdlc);
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(
new ReportDataSource(ds.DataSetName, ds.Tables[0]));
reportViewer1.RefreshReport();
}
}



public static Stream BuildRDLCStream(
DataSet data, string name, string reportXslPath, XsltArgumentList xslArgs)
{
using (MemoryStream schemaStream = new MemoryStream())
{
// save the schema to a stream
data.WriteXmlSchema(schemaStream);
schemaStream.Seek(0, SeekOrigin.Begin);

// load it into a Document and set the Name variable
XmlDocument xmlDomSchema = new XmlDocument();
xmlDomSchema.Load(schemaStream);
xmlDomSchema.DocumentElement.SetAttribute("Name", data.DataSetName);

// load the report's XSL file (that's the magic)
XslCompiledTransform xform = new XslCompiledTransform();
xform.Load(reportXslPath);

// do the transform
MemoryStream rdlcStream = new MemoryStream();
XmlWriter writer = XmlWriter.Create(rdlcStream);
if(xslArgs !=null) xform.Transform(xmlDomSchema, xslArgs, writer);
else xform.Transform(xmlDomSchema, writer);
writer.Close();
rdlcStream.Seek(0, SeekOrigin.Begin);

// send back the RDLC
return rdlcStream;
}
}

I found that I needed to remove the TableColumn template call

<xsl:apply-templates select="xs:element" mode="TableColumn">
</xsl:apply-templates>

and instead put in it's place (the variable $navColumns being a parameter that had been passed)

<xsl:for-each select="$navColumns">
<TableColumn>
<Width>
<xsl:value-of select=".">
</Width>
</TableColumn>
</xsl:for-each>

Alias field names

The names being used for the Header titles were that of the field/column names which don't have spaces and thus look very poorly formatted. Finding the SQL doesn't like aliased names with spaces I decided to format spaces (based on a Field Alias column I already had that was loaded into the class record along with all the other field details) to underscores and hyphens to astericks and then when the XSL file was 'used' it would translate these back into spaces and hyphens accordingly thus giving me a workaround solution.


Richard
Visionscape

Thursday, 28 May 2009

Merchant Shipping spotting/sighting software in development

A chance meeting in Glastonbury with a friend (John) who I hadn't seen for a number of years has lead me to starting the development of spotting software - which was something I had on my mind for a few months as a good projects to start using my new C#/.NET development skills.

We meet up a few days ago and discussed the many spotting interests he has (birds, planes, ships, trains, etc) and birds and merchant ships came to the fore and soon it was decided to begin with Merchant Ships.

Enthused by the wealth of information and knowledge of John had, I was soon planning the general look and structure of the application.

Now a couple of days later (and another meeting showing John my mock up) I'll start coding the application.


Richard
Visionscape

Kiwee - Fatal error: Call to a member function getChunk()

Installed Kiwee v1.0.7b onto the beginning of a new website (using MODx cms) and soon found that when I clicked on the tabs of some of the management sections under KiweeCommerce that I was getting the following error:

Fatal error: Call to a member function getChunk() on a non-object in C:\wamp\www\countryvintage\assets\
snippets\shoppingCart\eCart\include\eValues.inc.php

on line 240


I found that the line was trying to get a Chunk called 'gDiscountTplx' which was not installed under the HTMLSnippets section. Hunting through the 'installconfig.inc' file in the installation folder for HTMLSnippets I soon found that the problem was that although the entry existed the following (and last) Tpl file 'gCouponTplx' was incorrectly using the same index number for the $html array thus it was overwriting the details for 'gDiscountTplx' and therefore it was never to be installed.

This has been fixed for the any new release (which should be called v1.0.8)


Richard
Visionscape

KiweeCommerce - a renewed start after a long break

After a break from doing any development on Kiwee (KiweeCommerce - a Ecommerce module for the MODx CMS Framework) I am now making a renewed start (though it may be filled with breaks as I am using the development of an ecommerce website, for which I'm doing as a favour, as a means to check through what I have added to Kiwee and what other bugs still need ironing out and features to smooth out and maybe introduce).

I will attempt to comment on the progress of this work whether its solving bugs/issues or changing things or adding things (features, etc) through this blog and will do my best to respond to any queries and comments posted.


Richard
Visionscape

Friday, 22 May 2009

Spambot KIller ASP.NET

It had got to the point in developing my first ASP.Net website (for my first C#/.Net application - Accommodation Booking System) that I needed to explore a good way of 'encoding' any email addresses on the website. I had seen this mentioned from time to time over the many years but it wasn't until looking for something else did I feel that now was the time to see what other people were doing/suggesting.

ASP.NET Hyperlink Control

I soon found an article by Peter Bromberg called: Spambot Killer ASP.NET Mailto: Hyperlink Control where in Peter goes through some custom code that overrides certain Hyperlink functions and "... convert everything into it's HTML Entity representation." thus making it difficult for spambots to recognize as an email address.

Registering the Custom Control

Not having had much experience of Custom Controls I was suddenly finding it difficult to figure out how to Register this new feature.

Soon I found that the 'web.config' file could hold details of this and thus also making it globally available to the site, so I added the code under the 'controls' section

<add tagprefix="pab" namespace="PAB.WebControls"></add>

and sure enough now my page that was trying to use it was no longer complaining and I could remove my attempt at trying to Register it before the HTML code.

<pab:emaillink id="supportHyperLink" runat="server" tooltip="Support email address">

As you will notice I have missed out the NavigateURL attribute as well as Text. I have done this so I could add these in the code-behind file

supportEmail = ConfigurationSettings.AppSettings["SupportEmail"];
supportHyperLink.Text = supportEmail;
supportHyperLink.NavigateUrl = "mailto:" + supportEmail;

The supportEmail variable is a public string that I declared in the Class. This has the value of a AppSetting called 'SupportEmail' from the web.config file and then this value is attributed to Text and NavigateURL (prefixed with 'mailto:').

<pab:emailLink ID="supportHyperLink" runat="server" ToolTip="Support email address" / >

Adding/Changing the EmailLink Class

I noticed (of course) that the Text generated (which in my case is also the email address) was kept as plain text and although the function had done the job of obfuscating/encoding the email address as part of the hyperlink I calso needed the same process to happen to the Text attribute otherwise I would expect it would be all for nothing. I decided to change the else if relating to 'mailto:' code block under the Render function

link.NavigateUrl = HtmlObfuscate(link.NavigateUrl);
writer.WriteAttribute("href", link.NavigateUrl);
if(Text.IndexOf('@') > -1) Text = HtmlObfuscate(Text);

thus if the Text attribute is identified as having an '@' symbol in it it should be obfuscated as there is a good chance it is an email address.

In addition to this I added

if (!string.IsNullOrEmpty(link.ToolTip))
{
if (link.ToolTip.IndexOf('@') > -1) link.ToolTip = HtmlObfuscate(link.ToolTip);
writer.WriteAttribute("title", link.ToolTip);
}

again to make sure if it appeared to be an email address within the ToolTip (if there it wasn't null or empty) that it was obfuscated.


Richard
Visionscape

Friday, 1 May 2009

Installing and Running VS2008 developed application with Vista's UAC enabled

The problems with UAC

After testing my new application developed in Visual Studio 2008 (with C#) and seeing it working on both my development machine (running Vista) and my laptop with XP I thought I had covered the basics of the main Windows OSes but then when testing it on another Vista machine I realised I had problems, and that problem was User Access Control (UAC).
This hadn't been an issue on the development machine as I had UAC turned off (it bugged me too much - can't wait to see what Windows 7 various levels are like). So it was back to searching the net for help.
After searching on the errors that appeared as soon as the application was trying to write to the registry or write to the database (lack of permissions eg. 'Access to the database file is not allowed. [File name = ....'), I found mention of changing their permissions to overcome the errors. However, this didn't seem to work.
I decided to do some proper testing on my development machine with UAC enabled and soon found an issue with a Cryptography process

rsa = new RSACryptoServiceProvider(cspParams);

saying that the object already existed - this was possibly an issue, again, with UAC.
More searches and then I seemed to start getting somewhere.

Application Manifest file

First I came across:
VS2008: Embedding UAC Manifest Optionsthen

Enabling Your Application for UACthis latter article was just what I was looking for and (though I created the manifest per the former article). But like always another issue.


ClickOnce doesn't like 'requireAdministrator'

I had added my Application Manifest File (app.manifest), as suggested.
Changed the 'requestedExecutionLevel' to make 'level=requireAdministrator', but now when I tried to build my application I was getting a critical error complaining about this new setting.
Well then I need to disable ClickOnce as I'm deploying my application with Inno Setup - how do I disabled ClickOnce?

Disabling ClickOnce

I found the following thread on MSDN
How to remove "clickOnce" from an applicationwhere TaylorMitchell advises to check both the Signing and Security tabs of the project's properties - there may have been one or two other pointers in the thread so if those two don't switch ClickOnce off read through the rest.

Now with ClickOnce disabled I was able to continue with the previous article (Enabling Your Application for UAC) and soon I had my application with the Vista security shield showing.

After processing my application files as a setup file with my Inno configuration I found my last hurdle with UAC (well I hope it is :)).

Create Process failed; code 740

After the setup process came to an end and it then tried to launch my application the following error message showed

Unable to execute file c:\ .....

Create Process failed; code 740.

The requested operation requires elevation.


After more Googlings I soon found that the answer was down to a near simple running the setup
program as an Administrator (right-clicking on it and selecting 'Run as administrator').

This worked. The setup program completed as expected and then launched my application. The application ran without the errors that showed in relation to writing to the database or the registry - registry entries are being written using
Cryptography process on every use.


Richard
Visionscape

Thursday, 16 April 2009

Inno Setup - a simple, free setup program

Another first when developing my C# application was considering how I was going to publish it.

Although there is a Publish tool in Visual Studio/C# and it seemed simple (with lots of extras - some of which I knew I would have to read up on) there was one issue I seemed to find hard to get around and much Googling seemed to show that other people were saying the same thing.

Inclusion of .NET download feature

Yes, how do you include this. There was talk about using Visual Studio (2008) Setup and Depolyment Templates - I followed one or two tutorials and soon gave up when I didn't seem to have any idea of where you could (or how you could) add the ability for the system not only to download the required .NET Framework but to check the users system to see if existed and if so then to bypass the download.

Several hours of searching online and reading peoples comments and thinking I was almost there I came to the conclusion that there seemed to be one Setup application that was a front runner when I found details (code) that would allow the users system to be checked before consdiering whether the .NET download was required.

Inno Setup

Inno Setup (Jordan Russell's Software) was the one that appeared to be the answer. Not only was it getting sufficient praise/recommendation but it appeared to be both not overly difficult to program but also because of its program nature had the ability to offer good download conditioning.

I had soon installed Inno Setup Compiler (v 5.2.3) and was beginning my education in its way of setting up the process of the Setup program through it's INI structure.

The numerous examples and those found online plus the help documentation allowed me to get started and soon I had created a Setup app that allowed for the checking and then (if required) the downloading/installing of .NET 3.5 framework.

Then I added to this the need to check for and install the following
  • SQL Server Compact Edition 3.5 SP1 (file: SSCERuntime-ENU-x86.msi)
  • Report Viewer 2008 SP1 (file: reportviewer.exe)
  • MS Office 2003 Primary Interop Assemblies (file: O2003PIA.MSI)
With these last three I couldn't figure out from the MS website whether they should be downloaded (which is stated by them and other people in relation to the .NET framework) and as such I have included them as files which are installed (again if certain Registry enteries are missing - the latter one I had to work out as I couldn't find it in my searches on the internet).

For a FREE piece of software and with what was available (examples and other peoples work plus the supporting documentation) the Inno Setup application is just perfect to allow me to get started 'delivering' my first proper application.

The ISS file.


Richard
Visionscape