I have a client that wants to be able to produce a Customer Profile report based on data that resides in a SQL 2008 R2 db. They want to choose a customer from a drop-down list in a web page and have it spit out a rather extensive report on that customer.
I was going to use SSRS, but they wanted something that was not only functional but esthetically pleasing, so I'm merging data into a Word document using OpenXML.
Things have been going swimmingly, but I now have a request that I'm not sure how (or even if it is possible) to address with this.
The approach I've used so far is one of place-holders in a Word doc. You add a control from the Developer Tab, you name it, you wire it up to your XML and you populate it in your CSharp app. Works like a champ.
Now they want a kind of repeatable section thing. Let's say that a customer has open projects. For each of these projects, they would like to see the same set of data elements. So conceptually, I need a template of place holders that I
can populate and then merge into the document.
Here's some code:
privatevoid CreateDocument( )
{// Get the template document file and create a stream from itconststring DocumentFile = @"~/App_Data/CustomerProfileTemplate.docx";// const string DocumentFile = @"~/App_Data/OneOff.docx";// Read the file into memorybyte[ ] buffer = File.ReadAllBytes( Server.MapPath( DocumentFile ) );// MemoryStream memoryStream = new MemoryStream( buffer, true ); // NoNoNo!!! MemoryStreams created this way cannot expand dynamically!
MemoryStream memoryStream = new MemoryStream( );
memoryStream.Capacity = buffer.Length;
memoryStream.Write( buffer, 0, buffer.Length );
buffer = null;// Open the document in the stream and replace the custom XML partusing ( Package pkgFile = Package.Open( memoryStream, FileMode.Open, FileAccess.ReadWrite ) )
{
PackageRelationshipCollection pkgrcOfficeDocument = pkgFile.GetRelationshipsByType( strRelRoot );foreach ( PackageRelationship pkgr in pkgrcOfficeDocument )
{if ( pkgr.SourceUri.OriginalString == "/" )
{// Get the root part
PackagePart pkgpRoot = pkgFile.GetPart( new Uri( "/" + pkgr.TargetUri.ToString( ), UriKind.Relative ) );// Add a custom XML part to the package
Uri uriData = new Uri( "/customXML/item1.xml", UriKind.Relative );if ( pkgFile.PartExists( uriData ) )
{// Delete document "/customXML/item1.xml" part
pkgFile.DeletePart( uriData );
}// Load the custom XML data
PackagePart pkgprtData = pkgFile.CreatePart( uriData, "application/xml" );
GetDataFromSQLServer( pkgprtData.GetStream( ), ddlCustomer.SelectedValue );
}
}// Close the file - not needed thanks to "Using"// pkgFile.Close( );
}//Build File Namestring fileName = ddlCustomer.SelectedItem.ToString( ) + " Profile.docx";// Return the result
Response.ClearContent( );
Response.ClearHeaders( );
Response.AddHeader( "content-disposition", "attachment; filename=" + fileName );
Response.ContentEncoding = System.Text.Encoding.UTF8;
memoryStream.WriteTo( Response.OutputStream );
memoryStream.Close( );
Response.End( );
}
The function GetDataFromSQLServer( ) is where I get the data and drop it into the doc and looks something like this:
privatevoid GetDataFromSQLServer( Stream stream, string customerID = "" )
{// Connect to a Microsoft SQL Server database and get data
String source = System.Configuration.ConfigurationManager.ConnectionStrings[ "BlahBlahBlahConnectionString" ].ConnectionString;using ( SqlConnection conn = new SqlConnection( source ) )
{
conn.Open( );
SqlCommand cmd = new SqlCommand( );
cmd.CommandText = "dbo.usp_get_data";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
cmd.Parameters.AddWithValue( "@CompanyId", customerID );
SqlDataReader dr = cmd.ExecuteReader( );if ( dr.Read( ) )
{
XmlWriter writer = XmlWriter.Create( stream );// Get Data string companyName = ( string )dr[ "CompanyName" ];string numEmployees = ( string )dr[ "NumberOfEmployees" ].ToString( );string technologyID = ( string )dr[ "TechnologyID" ];
. . .
. . .
. . .// Title Page
writer.WriteStartElement( "Customer" );
writer.WriteElementString( "CompanyName", companyName );
writer.WriteElementString( "TechnologyID", technologyID );
writer.WriteElementString( "NumberOfEmployees", numEmployees );
. . .
. . .
. . .
writer.WriteEndElement( );
writer.Close( );
}
dr.Close( );
conn.Close( );
}
}
Shameful lack of error handling aside, the above process works nicely; the resulting document is beautiful. The problem is I don't see a way forward from here given the latest requests.
Any thoughts would be very much appreciated.
JP