Click here to Skip to main content
15,312,747 members
Articles / Programming Languages / C#
Posted 8 Apr 2009


164 bookmarked

Advanced Report Viewer

Rate me:
Please Sign up or sign in to vote.
4.79/5 (32 votes)
10 Apr 2009CPOL6 min read
The article shows how to extend ReportViewer control that comes with Visual Studio 2008. The most important extension is adding export to Microsoft Word


The article shows how to extend the ReportViewer control that comes with Visual Studio 2008. The most important extension is adding export to Microsoft Word.


Article Highlights

  • Integration into WPF application
  • Object data source usage
  • Export to Microsoft Word
  • Localization
  • Customization


When the time came to chose report engine, I chose Microsoft Reporting Services. It is based on open RDL format and mature enough. ReportViewer that comes with the Studio can be used for free (no need for SQL Server license). It doesn't require any connection to SQL Server either. And it can export to Microsoft Excel and Adobe PDF.

Over time, new requirement arises - Export to Microsoft Word. New version of ReportViewer that is included in SQL Server 2008 and will be included in Visual Studio 2010 can do that. The version that comes with Visual Studio 2008 SP1 can't. But we need that export functionality now.

Export to Microsoft Word, Approach

The simplest solution - export to *.pdf and then from *.pdf to *.doc. But since my reports are in Russian, it can't be done. There is some issue with encoding that makes the resulting *.doc unreadable. Of course there are some third-party RDL renders to Microsoft Word. But they are not for free. After some research, an insight came:

  • Export to HTML can be enabled
  • If you save *.html as *.doc, Microsoft Word will open it

Export to HTML is included in local ReportViewer's code but it's switched off. The article was found how to re-enable it (see links section). Resulting *.do? is not truly a Microsoft Word document. But it can be edited with Microsoft Word and looks like *.doc. If it is not enough one can use Microsoft Word COM Interop library and save it in the native *.doc format. The other issue with this approach - there won't be page header and footer on each page.

NOTE: You may be wondering why can't I just take new ReportViewer from SSRS 2008. The answer is - there is no local ReportViewer control there. Only the server-side one. I learned it the hard way. Downloaded SQL Server 2008 Reporting Services Report Builder 2.0. Searched for ReportViewer DLL and tried to instantiate it. Exception arose - CreateLocalReport method is not implemented.

Integration into WPF Application

We need a simple way of communicating with the control. To use it in XAML, we need two dependency properties:

  • Source of the RDLC report (RDLC stands for client RDL)
  • Source of data for the report
    DataSource="{StaticResource samplePerson}" />

Where DataSource is an object that contains data for the report and EmbeddedReport is a string that tells where to search for the RDLC report.

EmbeddedReport is a simple one. It sets reportViewer.LocalReport.ReportEmbeddedResource property. RDLC reports are added to the project as embedded resources. So to use them, one must specify the fully qualified path - default namespace.reports file name.

Object Data Source Usage

DataSource is a tricky one. ReportViewer awaits data in a special format.

  • It works only with IListSource (DataTables) and IEnumerable (Collections)
  • Name of the data source must match its type
  • ReportDataSource wrapper must be used

After some time of trial-and-errors, the code was written:

private static ReportDataSource CreateReportDataSource( object originalDataObject )
    string name = originalDataObject.GetType( ).ToReportName( );
    object value = originalDataObject;

    // DataTable
    if( originalDataObject is IListSource )
    // Collection
    if( originalDataObject is IEnumerable )
        name = GetCollectionElementType( originalDataObject ).ToReportName( );
    // Just an object
        value = new ArrayList { originalDataObject };

    Debug.Assert( !string.IsNullOrEmpty( name ), 
		"Data source's name must be defined " );
    Debug.Assert( value != null, "Data source must be defined" );
    return new ReportDataSource( name, value );

Where Type.ToReportName( ) is:

public static string ToReportName( this Type type )
    var isTypedDataTable =
        type.IsNested &&
        type.BaseType.FullName.StartsWith( "System.Data.TypedTableBase" );

    if( isTypedDataTable )
        // in:  Some.Namespace.CategoryDataSet+CategoryDataTable
        // out: CategoryDataSet_Category
        var match = Regex.Match( type.FullName, @"^.+\.(\w+\+\w+)DataTable$" );
        return match.Groups[ 1 ].Value.Replace( "+", "_" );
        // in:  Some.Namespace.TypeName
        // out: Some_Namespace_TypeName
        return type.FullName.Replace( ".", "_" );

It may seem strange to use a report that shows data of a single object. But it was in fact the main use case for my project. Anyway, the code is flexible enough to accept collections, DataSets and DataTables as data as well.

Export to Microsoft Word

Articles [3] and [4] tell us how to extend render capabilities of a ReportViwer. In short - there is render to HTML but it is off and it is hard-coded. To change the situation, one must change RenderingExtensions.

RenderingExtensions = 
	reportViewer.LocalReport.m_previewService.ListRenderingExtensions( )

Each extension contains self explanatory fields:

  • Name - internal name of a rendering extension
  • m_localizedName - localized name that is shown in Export dropdown
  • m_isVisible, m_isExposedExternally - visibility and availability to the end user

But all mentioned code is not public. In fact, we have to use reflection to modify it.

private IList RenderingExtensions
        var service = reportViewer.LocalReport
            .GetType( )
            .GetField( "m_previewService",
		BindingFlags.NonPublic | BindingFlags.Instance )
            .GetValue( reportViewer.LocalReport );

        var extensions = service
            .GetType( )
            .GetMethod( "ListRenderingExtensions" )
            .Invoke( service, null );

        return (IList) extensions;
private void EnableRenderExtension( string extensionName, string localizedExtensionName )
    foreach( var extension in RenderingExtensions )
        // name = extension.Name;
        var name = extension
            .GetType( )
            .GetProperty( "Name" )
            .GetValue( extension, null )
            .ToString( );

        if( name == extensionName )
            // extension.m_isVisible = true;
                .GetType( )
                .GetField( "m_isVisible",
			BindingFlags.NonPublic | BindingFlags.Instance )
                .SetValue( extension, true );

            // extension.m_isExposedExternally = true;
                .GetType( )
                .GetField( "m_isExposedExternally",
			BindingFlags.NonPublic | BindingFlags.Instance )
                .SetValue( extension, true );

            // extension.m_localizedName = localizedExtensionName;
                .GetType( )
                .GetField( "m_localizedName",
			BindingFlags.NonPublic | BindingFlags.Instance )
                .SetValue( extension, localizedExtensionName );

HTML rendering extension has internal name HTML4.0. So enabling export to *.html will be:

EnableRenderExtension( "HTML4.0", "MS Word" );

Now we need to change "Export to Microsoft Word" handler. Otherwise we will get ".html" file. First let's switch off ReportViewer's export dialog that appears when "Export to Microsoft Word" clicked:

reportViewer.ReportExport += ( sender, args ) =>
	args.Cancel = args.Extension.LocalizedName == "MS Word";

Now we need to modify behaviour of a control when the Export to Microsoft Word button is clicked. Finding that button is an interesting story. Did you know that ReportViewer's ToolStrip can be accessed with public interface?

private T FindControl<t>( System.Windows.Forms.Control control )
    where T: System.Windows.Forms.Control
    if( control == null ) return null;

    if( control is T )
        return (T) control;

    foreach( System.Windows.Forms.Control subControl in control.Controls )
        var result = FindControl<t>( subControl );
        if( result != null ) return result;

    return null;

And ToolStrip is:

ToolStrip = FindControl<>( reportViewer );

All controls on that ToolStrip have friendly names (see yourself with reflector =). Export button has name "export". On DropDown its sub-buttons are created. One of those buttons is Export to Microsoft Word that we are looking for.

// Change export button handler
var exportButton = ToolStrip.Items[ "export" ] as

// Buttons are created on DropDownOpened so we can't assign handler before it
exportButton.DropDownOpened += delegate( object sender, EventArgs e )
    var button = sender as System.Windows.Forms.ToolStripDropDownButton;
    if( button == null ) return;

    foreach( System.Windows.Forms.ToolStripItem item in button.DropDownItems )
        var extension = (RenderingExtension) item.Tag;
        if( extension.LocalizedName == "MS Word" )
            item.Click += MSWordExport_Handler;

When the user clicks on it, MSWordExport_Handler will be called.

private void MSWordExport_Handler( object sender, EventArgs args )
    // Ask user where to save
    var saveDialog = new SaveFileDialog
        FileName = ReflectionHelper.GetPropertyValue
		( reportViewer.LocalReport, "DisplayNameForUse" ) + ".doc",
        DefaultExt = "doc",
        Filter = "MS Word (*.doc)|*.doc|All files (*.*)|*.*",
        FilterIndex = 0,
    if( saveDialog.ShowDialog( ) != true ) return;

    // Create a report
    Warning[] warnings;
    using( var stream = File.Create( saveDialog.FileName ) )
            (CreateStreamCallback) delegate { return stream; },
            out warnings );

    // Show user all warnings
    // NOTE: Default export handler doesn't do that
    if( warnings.Length > 0 )
        var builder = new StringBuilder( );
        builder.AppendLine( "Please take notice that:" );

        warnings.Action( warning => builder.AppendLine( "- " + warning.Message ) );

            builder.ToString( ),
            MessageBoxImage.Warning );

    // Open created report
    // Process.Start( saveDialog.FileName );

ReflectionHelper is a class that gets the private property value for default report name. ExpandContent instructs renderer to use tables with width of a page. Otherwise content will be squeezed. Render method accepts a delegate that returns a stream. Render is performed on that stream. The stream is not closed on Render's exit so we must to ensure it ourselves. Also, default export handlers don't do that, but I think it's helpful to show warnings of a report.

NOTE: There is one more issue to highlight. Using reflection, one can add custom renderer to ReportViewer. Why not take Word renderer from SSRS 2008? The answer is - the interfaces are incompatible. And yes, I learned it the hard way.


To localize ReportViewer GUI interface one must implement IReportViewerMessages and assign an instance to Messages property. But let's make something more useful. Let the user decide what language he wants. To do that, we need some control that will list all languages available. And we need to host that control somewhere. Somewhere on the ReportViewer will be perfect.

private void InitializeLocalization( )
    var separator = new System.Windows.Forms.ToolStripSeparator( );
    ToolStrip.Items.Add( separator );

    var language = new System.Windows.Forms.ToolStripComboBox( );
    language.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
    language.Items.Add( "English" );
    language.Items.Add( "Русский" );
    language.SelectedIndex = 0;
    language.SelectedIndexChanged += delegate
        switch( (string) language.SelectedItem )
        case "English":
            reportViewer.Messages = null;

        case "Русский":
            reportViewer.Messages = new RussianReportViewerMessages( );

            Debug.Assert( false, "Unknown language: " +
				(string) language.SelectedItem );
    ToolStrip.Items.Add( language );


As a dessert, let's change some icons of ReportViewer to make it more attractive.

private void InitializeIcons( )
    // Fix find icons
    ToolStrip.Items[ "find" ].Image = Properties.Resources.Report_Find;
    ToolStrip.Items[ "findNext" ].Image = Properties.Resources.Report_FindNext;

    // Fix export icons
    var exportButton = ToolStrip.Items[ "export" ] as

    // Buttons are created on DropDownOpened so we can't assign Icon before it
    exportButton.DropDownOpened += delegate( object sender, EventArgs e )
        var button = sender as System.Windows.Forms.ToolStripDropDownButton;
        if( button == null ) return;

        foreach( System.Windows.Forms.ToolStripItem item in button.DropDownItems )
            var extension = (RenderingExtension) item.Tag;

            switch( extension.LocalizedName )
            case "MS Word":
                item.Image = Properties.Resources.Report_Word;

            case "MS Excel":
                item.Image = Properties.Resources.Report_Excel;

            case "Adobe PDF":
                item.Image = Properties.Resources.Report_PDF;

Using the Code

You can use whole ReportViewerUserControl that comes with the code.

    DataSource="{StaticResource samplePerson}" />

Or you can use just the aspect you need. I'd suggest exploring aspects from ReportViewerUserControl.InitializeReportViewer.


The article would not have been created without:

  1. - way to customize
  2. - way to localize
  3. - way to explore
  4. - way to render


  • 09 April 2009 - First publication
  • 10 April 2009 - Fixed bug in CreateReportDataSource

Thoughts on Version 2

If this article will be successful, I'd highlight the following aspects in version 2:

  • Subreports with object data source
  • Report codegeneration
  • Using custom code in reports
  • Using Microsoft Word COM interop to convert to true *.doc


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Alexander Kostikov
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

QuestionCannot create instance of "ReportViewerControl" Pin
kamelfahd17-Aug-15 3:35
Memberkamelfahd17-Aug-15 3:35 
QuestionNot able to add new Dataset in your Samplereport.rdlc Pin
neha2419874-Nov-14 23:50
Memberneha2419874-Nov-14 23:50 
QuestionWhat is GetCollectionElementType Pin
Member 207364411-Apr-13 5:38
MemberMember 207364411-Apr-13 5:38 
QuestionSqlException was unhandled Pin
EricGeerts2-Oct-12 5:30
MemberEricGeerts2-Oct-12 5:30 
GeneralMy vote of 5 Pin
Shahin Khorshidnia6-Feb-12 10:21
professionalShahin Khorshidnia6-Feb-12 10:21 
QuestionHow to configure Microsoft reportviewer for the japanese operating system ? [modified] Pin
shekhar.gurav1318-Jul-11 20:49
Membershekhar.gurav1318-Jul-11 20:49 
QuestionHow to use Image Pin
Anubhava Dimri20-Feb-11 20:53
MemberAnubhava Dimri20-Feb-11 20:53 
GeneralThanks Pin
whbing29-Nov-10 14:47
Memberwhbing29-Nov-10 14:47 
GeneralExtending render engine (align justify) Pin
mirand18-Nov-10 13:29
Membermirand18-Nov-10 13:29 
GeneralRe: Extending render engine (align justify) Pin
Alexander Kostikov18-Nov-10 13:39
MemberAlexander Kostikov18-Nov-10 13:39 
GeneralVisual Studio 2010 Pin
spv09811-Oct-10 22:54
Memberspv09811-Oct-10 22:54 
GeneralRe: Visual Studio 2010 Pin
Alexander Kostikov12-Oct-10 14:08
MemberAlexander Kostikov12-Oct-10 14:08 
GeneralI want same article for web Pin
qeqwerqwrwewe4-Oct-10 1:29
Memberqeqwerqwrwewe4-Oct-10 1:29 
GeneralMy vote of 5 Pin
qeqwerqwrwewe4-Oct-10 1:22
Memberqeqwerqwrwewe4-Oct-10 1:22 
QuestionHow to use as replacement for MS Report Viewer Pin
JohnLeo12-May-10 6:28
MemberJohnLeo12-May-10 6:28 
GeneralExport to Word in ContextMenu Pin
fsandner18-Feb-10 23:34
Memberfsandner18-Feb-10 23:34 
GeneralRe: Export to Word in ContextMenu Pin
Alexander Kostikov18-Feb-10 23:55
MemberAlexander Kostikov18-Feb-10 23:55 
Questiondo not render data in IE Pin
A.Petkovic1-Feb-10 0:19
MemberA.Petkovic1-Feb-10 0:19 
AnswerRe: do not render data in IE Pin
Alexander Kostikov1-Feb-10 4:52
MemberAlexander Kostikov1-Feb-10 4:52 
GeneralUsing this code in Web application Pin
stchomdom11-Oct-09 4:29
Memberstchomdom11-Oct-09 4:29 
GeneralRe: Using this code in Web application Pin
Alexander Kostikov11-Oct-09 6:37
MemberAlexander Kostikov11-Oct-09 6:37 
GeneralRe: Using this code in Web application [modified] Pin
stchomdom11-Oct-09 22:07
Memberstchomdom11-Oct-09 22:07 
GeneralRe: Using this code in Web application Pin
Alexander Kostikov11-Oct-09 23:40
MemberAlexander Kostikov11-Oct-09 23:40 
GeneralRe: Using this code in Web application Pin
stchomdom16-Oct-09 4:09
Memberstchomdom16-Oct-09 4:09 
GeneralRe: Using this code in Web application Pin
Alexander Kostikov16-Oct-09 5:04
MemberAlexander Kostikov16-Oct-09 5:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.