Saturday, January 22, 2011

Display Html in RDLC file

I want to display HTML in RDLC of reporting service. Report Viewer support html but with limited tags, I want to display HTML generated from rich text editor. After investigation I found the best solution is displaying it as image. Steps to do that:

1. Create class library convert HTML to image.
2. Use this class with report viewer.
3. Display report with asp net.

1. Create class library convert HTML to image
a. In the following url http://webapplication02.blogspot.com/2011/01/convert-html-to-image.html you find the way to create class library to convert html to image.
It is better to create image as bmp for better image quality but this will not work with excel.
You can convert image to another type but check its quality.
b. Need to add [assembly: AllowPartiallyTrustedCallers()] to trust class library for using by report viewer.

2. Use Class with report viewer:
a. Replace your html field in report with Image with properties:
i. Mime type image/bmp
ii. Type of field Database
iii. Field Expression =Code.GetHtmlImage(HtmlField)

b. Add this function to report:


Public Function GetHtmlImage(ByVal body as String) As Byte()
return htmlConv.getWebPageBytes(body, nothing, nothing)
End Function


c. Add Assembly reference to 3 dlls:
i. System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
ii. System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
iii. Your class library
d. Add class library ImageConv class and instance htmlConv
e. To build your solution with report reference to your class library copy your class library to folder:
($Program Files Path)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies

3. Display Report with ASPNET:


ReportViewer1.LocalReport.ExecuteReportInCurrentAppDomain(System.Reflection.Assembly.GetExecutingAssembly().Evidence);
ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain("System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain(Full Assembly name of your class libarary);

Also need to add Full Trust in Web.Config

Convert Html to Image

I searched for free open source csharp to convert html to image. I found solution using:

WebBrowser (namespace System.Windows.Forms) code below is sample of conversion:


static byte[] CaptureWebPageBytesP(string body, int? width, int? height)
{
byte[] data;
// create a hidden web browser, which will navigate to the page
using (WebBrowser web = new WebBrowser())
{
web.ScrollBarsEnabled = false; // we don't want scrollbars on our image
web.ScriptErrorsSuppressed = true; // don't let any errors shine through
web.Navigate("about:blank");
// wait until the page is fully loaded
while (web.ReadyState != System.Windows.Forms.WebBrowserReadyState.Complete)
System.Windows.Forms.Application.DoEvents();
web.Document.Body.InnerHtml = body;

// set the size of our web browser to be the same size as the page
if (width == null)
width = web.Document.Body.ScrollRectangle.Width;
if (height == null)
height = web.Document.Body.ScrollRectangle.Height;
web.Width = width.Value;
web.Height = height.Value;
// a bitmap that we will draw to
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(width.Value, height.Value))
{
// draw the web browser to the bitmap
web.DrawToBitmap(bmp, new Rectangle(web.Location.X, web.Location.Y, web.Width, web.Height));
// draw the web browser to the bitmap
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
EncoderParameter qualityParam = null;
EncoderParameters encoderParams = null;
try
{
ImageCodecInfo imageCodec = null;
//imageCodec = getEncoderInfo("image/jpeg");
imageCodec = getEncoderInfo("image/bmp");

// Encoder parameter for image quality
qualityParam = new EncoderParameter(Encoder.Quality, 100L);

encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
bmp.Save(stream, imageCodec, encoderParams);
}
catch (Exception)
{
throw new Exception();
}
finally
{
if (encoderParams != null)
encoderParams.Dispose();
if (qualityParam != null)
qualityParam.Dispose();
}
bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
data = new byte[stream.Length];
stream.Read(data, 0, (int)stream.Length);
}
}
}
return data;
}

But I faced problem when I try to use it without Window Forms (console application, aspnet, or windows service) It gave me error cannot be instantiated because the current thread is not in a single-threaded. To solve this error created STAThread like code below:

public static byte[] CaptureWebPageBytes(string body, int? width, int? height)
{
bool bDone = false;
byte[] data = null;
DateTime startDate = DateTime.Now;
DateTime endDate = DateTime.Now;

//sta thread to allow intiate WebBrowser
var staThread = new Thread(delegate()
{
data = CaptureWebPageBytesP(body, width, height);
bDone = true;
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
while (!bDone)
{
endDate = DateTime.Now;
TimeSpan tsp = endDate.Subtract(startDate);

Application.DoEvents();
if (tsp.Seconds > 50)
{
break;
}
}
staThread.Abort();
return data;
}