Use OpenOffice Uno CLI with C# to create a spreadsheet - c#

I have so far found a couple of sources that discuss creation of ODS files: How to create ODS documents in .Net and
How to create .odt files with C#.NET?
And most interestingly an explanation for opening calc files. However this opens OpenOffice in fullscreen, what im looking for is some way to write to a Calc file (.ods) without actually opening Openoffice. So that I can write a function that just opens a savefiledialog, gets the filename and then creates and saves the .ods file.
Are there is any C# code examples available to do such a thing?

So I have finally resolved this issue and want to save others the hazel of going through this again. Basic points of HEADACE for me were:
Use forward slashes instead of backward slashes (e.g. its C:/ not C:\ )
The Filtername used should be set to the engine used to save the document. Possible values include writer8, calc8, MS Excel 97, so for spreadsheets you obviously need to use calc8
If you dont want that OpenOffice pops up in the forground and wait for it to get filled with your data, then use the PropertyValue and set Hidden to true.
Happy coding and dont forget to install the OpenOffice SDK to be able to add the unoidl references:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.sheet;
using unoidl.com.sun.star.container;
using unoidl.com.sun.star.table;
using unoidl.com.sun.star.text;
namespace TimeScanner {
class ReportGenerator {
private const string fileName =
#"file:///C:/Documents and Settings/My Documents/Hours Report.ods";
//Concrete Methods
internal XComponent openCalcSheet() {
XComponentContext oStrap = uno.util.Bootstrap.bootstrap();
XMultiServiceFactory oServMan = (XMultiServiceFactory)oStrap.getServiceManager();
XComponentLoader desktop = (XComponentLoader)oServMan.createInstance("com.sun.star.frame.Desktop");
string url = #"private:factory/scalc";
PropertyValue[] loadProps = new PropertyValue[1];
loadProps[0] = new PropertyValue();
loadProps[0].Name = "Hidden";
loadProps[0].Value = new uno.Any(true);
//PropertyValue[] loadProps = new PropertyValue[0];
XComponent document = desktop.loadComponentFromURL(url, "_blank", 0, loadProps);
return document;
}
public void writeToSheet(XComponent document) {
XSpreadsheets oSheets = ((XSpreadsheetDocument)document).getSheets();
XIndexAccess oSheetsIA = (XIndexAccess) oSheets;
XSpreadsheet sheet = (XSpreadsheet) oSheetsIA.getByIndex(0).Value;
XCell cell = sheet.getCellByPosition( 0, 0 ); //A1
((XText)cell).setString("Cost");
cell = sheet.getCellByPosition( 1, 0 ); //B1
cell.setValue(200);
cell = sheet.getCellByPosition( 1, 2 ); //B3
cell.setFormula("=B1 * 1.175");
}
public void saveCalcSheet(XComponent oDoc) {
PropertyValue[] propVals = new PropertyValue[1];
propVals[0] = new PropertyValue();
propVals[0].Name = "FilterName";
propVals[0].Value = new uno.Any("calc8");
((XStorable)oDoc).storeToURL(fileName, propVals);
}
}
}

Related

LibreOffice Calc C# SDK: program to insert images into cells, stuck trying to create XGraphic

Background: I'm trying to write a program to insert an image into a cell of a spreadsheet. LibreOffice recently changed how this is done, and all the samples I could find use the old method which no longer works.
Technically I know that you can't "insert" an image into a cell and that such an image is an overlay on a DrawPage that sits on top of the spreadsheet to "decorate" it.
One of the first steps in doing this (the new way) is to create an XGraphic object which contains the image. The process is to create an XGraphicProvider and call it with MediaProperties that specify the image file URL to be loaded. I have a program that is supposed to do this but the resulting XGraphic is null. The LO SDK gives pretty much no information when you do something wrong; it just doesn't work.
Here is the code I have, with all the headers removed:
// addpic
// add picture to spreadsheet - debug version
class OpenOfficeApp {
[STAThread]
static void Main(string[] args) {
bool lreadonly;
string pqfile;
string pqURL;
string pqpic;
pqfile = "file:///D:/Documents/NSexeye/ODS%20File%20Access/"+
"addpix/addpic.ods";
pqpic = "addpic2";
pqURL = pqpic+".jpg";
lreadonly = false;
Console.WriteLine("Using: "+pqfile);
// get the desktop
XComponentContext XCC = uno.util.Bootstrap.bootstrap();
XMultiComponentFactory XMCF =
(XMultiComponentFactory)XCC.getServiceManager();
XMultiServiceFactory XMSF = (XMultiServiceFactory)XCC.getServiceManager();
XComponentLoader XCL =
(XComponentLoader)XMSF.createInstance("com.sun.star.frame.Desktop");
// open the spreadsheet
PropertyValue[] pPV = new PropertyValue[2];
pPV[0] = new PropertyValue();
pPV[0].Name = "Hidden";
pPV[0].Value = new uno.Any(true);
pPV[1] = new PropertyValue();
pPV[1].Name = "ReadOnly";
if (lreadonly) pPV[1].Value = new uno.Any(true);
else pPV[1].Value = new uno.Any(false);
XComponent XCo = XCL.loadComponentFromURL(pqfile,"_blank",0,pPV);
// create graphic object containing image
object oGP = XMCF.createInstanceWithContext(
"com.sun.star.graphic.GraphicProvider",XCC);
if (oGP == null) {
Console.WriteLine("oGP is null. Aborting.");
return;
}
XGraphicProvider XGP = (XGraphicProvider)oGP;
if (XGP == null) {
Console.WriteLine("XGP is null. Aborting.");
return;
}
pPV = new PropertyValue[1];
pPV[0] = new PropertyValue();
pPV[0].Name = "URL";
pPV[0].Value = new uno.Any(pqURL);
Console.WriteLine("Creating XGraphic containing "+pqURL);
XGraphic XG = XGP.queryGraphic(pPV);
// *** XG is null here
if (XG == null) {
Console.WriteLine("XG is null. Aborting.");
return;
}
// ... lots of stuff to be added here
// save and close the spreadsheet
XModifiable XM = (XModifiable)XCo;
XM.setModified(true);
XStorable XSt = (XStorable)XCo;
XSt.store();
XCloseable XCl = (XCloseable)XCo;
XCl.close(true);
// terminate LibreOffice
// *** I want this to not terminate it if something else is open
XDesktop XD = (XDesktop)XCL;
if (XD != null) XD.terminate();
}
}
I get a null for the XGraphic, in the place indicated in the comments. I don't know if the call to create it is failing, or if one of the earlier steps of the process are incorrect.
My goal here, in addition to getting my program working, is to create a sample program showing how to add an image to a Calc spreadsheet cell, and to manipulate such images. There are a fair number of people asking questions about this and none of the examples I've found will work. I think a good working sample will be of value.
I've spent a lot of time searching for information and code samples for this, with nothing that helps. I've tried to find ways to verify the validity of the XGraphicProvider interface with no luck. I've run out of things to try.
I'm hoping someone who knows about the LibreOffice SDK can take a look and maybe see what I'm doing wrong.
Update: I figured out what I was doing wrong: I was passing a bare filename in the "URL" property to XGraphicProvider. It has to be the same format (starting with "file:///") as the spreadsheet's file name specification.
Now I'm stuck with another property problem. The XGraphic has to be specified as a parameter to the GraphicObjectShape's Graphic property, but the setPropertyValue() function requires that it be a uno.Any type. I can't figure out how to specify an interface name like XGraphic as a uno.Any.
Here is the piece of code that won't compile, complaining that it can't convert an XGraphic to a uno.Any, in the first setPropertyValue call:
// set image XGraphic
XPropertySet XPS = (XPropertySet)XS;
XPS.setPropertyValue("Graphic",XG);
XPS.setPropertyValue("Name",new uno.Any(pqpic));
XG is an XGraphic type. Using "new uno.Any(XG)" doesn't work either, giving a similar compiler error.
After trying unsuccessfully for a few hours to get the latest LO SDK up and running, let me offer some untested ideas.
First of all, here is some working Basic code, no doubt similar to what you're translating from. The important line is oShape.Graphic = oProvider.queryGraphic(Props()).
oDoc = ThisComponent
oSheet = oDoc.CurrentController.ActiveSheet
pqURL = "file:///C:/Users/JimK/Desktop/addpic.jpg"
oProvider = createUnoService("com.sun.star.graphic.GraphicProvider")
oShape = oDoc.createInstance("com.sun.star.drawing.GraphicObjectShape")
Dim Props(0) as new com.sun.star.beans.PropertyValue
Props(0).Name= "URL"
Props(0).Value = pqURL
oShape.Graphic = oProvider.queryGraphic(Props())
oCell = oSheet.getCellByPosition(5,5)
oShape.Name = oCell.AbsoluteName + "##" + Props(0).Value
oShape.Anchor = oCell
oSheet.DrawPage.add(oShape)
'Resize
w = oShape.Graphic.Size.Width
h = oShape.Graphic.Size.Height
wcl = oCell.Size.Width
hcl = oCell.Size.Height
If w<>0 and h<>0 then
oCell.String=""
Dim Size as new com.sun.star.awt.Size
Size.Width = wcl
Size.Height = h*wcl/w
If Size.Height > hcl then
Size.Width = hcl*w/h
Size.Height = hcl
Endif
oShape.setSize(Size)
oShape.setPosition(oCell.Position)
erase oShape
Else
oShape.dispose()
Endif
Now, how to translate this to C#? It looks like you may need to explicitly specify the type. In the SDK example, there are calls like this.
xFieldProp.setPropertyValue(
"Orientation",
new uno.Any(
typeof (unoidl.com.sun.star.sheet.DataPilotFieldOrientation),
unoidl.com.sun.star.sheet.DataPilotFieldOrientation.DATA ) );
So in your case, something like this:
XPS.setPropertyValue(
"Graphic"
new uno.Any(
typeof(unoidl.com.sun.star.graphic.XGraphic),
XG));
Alternatively, follow the suggestion here: set GraphicURL, which should load the image and set Graphic for you.

Add column values while uploading a file to Sharepoint with C#

Currently I'm working on a project that requires the upload of files to Sharepoint. So far i can upload files programmatically without problems:
class SPAPI
{
internal void SPUploader(Stream fs, string filename)
{
ClientContext context = new ClientContext("https://....de");
System.Net.ICredentials creds = System.Net.CredentialCache.DefaultCredentials;
context.Credentials = creds;
context.RequestTimeout = 60000000; // Time in milliseconds
string fnUrl = "/software/ap_ck/Dokumenten Management System/" + filename;
Microsoft.SharePoint.Client.File.SaveBinaryDirect(context, fnUrl, fs, true);
}
}
Additionally i also have to add specific values (together with the file) to the columns in the Sharepoint document list. As this is the first time for me working with Sharepoint some of the solutions found online are a bit confusing.
One solution contains the following line:
List docList = site.Lists.GetByTitle("members");
I can only guess that the "Lists" are the storages for the files? How do i get the title of my list where i upload each files to? Sry for that stupid question, but i have never worked with Sharepoint before.
We can use Event receiver to achieve this.
First create a event receiver as below.
We can add column values after the item added(document uploaded) as below.
using System;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using System.Web;
using System.Text;
using System.Data.SqlClient;
namespace SharePointFarmSolutionDev.DocumentEventReceiver
{
/// <summary>
/// List Item Events
/// </summary>
public class DocumentEventReceiver : SPItemEventReceiver
{
private readonly HttpContext _currentContext;
public DocumentEventReceiver()
{
if (_currentContext == null)
{
_currentContext = HttpContext.Current;
}
}
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
//init
Guid listId = properties.ListId;
int itemId = properties.ListItemId;
SPSite site = properties.Site;
SPWeb web = properties.Web;
SPList list = web.Lists[listId];
SPListItem item = list.GetItemById(itemId);
item["title"] = "hello";
item.Update();
}
}
}
More information about Event Receiver, we can refer to: Introduction To SharePoint Event Receivers
I would suggest you highlight which version of SP you are working with to get a better answer. Once you have performed the above code you would need to get a handle of the object you have just uploaded.
Uri filename = new Uri("Your file Url");
string server = filename.AbsoluteUri.Replace(filename.AbsolutePath, "");
string serverrelative = filename.AbsolutePath;
Microsoft.SharePoint.Client.File file= web.GetFileByServerRelativeUrl(serverrelative);
ListItem lstItem = file.ListItemAllFields;
clientContext.Load(lstItem);
clientContext.ExecuteQuery();
Once you have got the list handle, you can update the editor field as below
lstItem[<Field Name Here>] = <value here>
lstItem.Update();
clientContext.ExecuteQuery();
I have stolen the answer from a blog here but this should be a good starting point.
It worth remembering when you are working with doc libs you have the file you upload and an associated ListItem, Your code deals with the upload and the piece of code I have copied deals with the list item.
I've not tested this but I'm pretty sure this is how you would have to do it.
Cheers
Truez

Programmatically script SSIS package - Dynamic XML to OLE DB

I've been trying to piece together how other users have finished their projects, but my understanding is still limited.
I want to take any given XML source, make a Data Flow Task, and pass its data to an OLE DB destination matching the table name of the XML file. Running it with the visual tool means I cannot do dynamic data flow tasks because the Metadata does not refresh.
I have created a script that creates a package, but when I open the package in Visual Studio, it has a red-x saying that there cannot be zero Input Columns. When I drill down and look at the mappings of the OLE DB Destination, then click OK - it corrects it for me. I cannot figure out how to do that programmatically.
I've seen others solve it by using foreach loops and going through the Input columns, but I cannot seem to figure it out.
I also have a separate script that I tried to mimic several people's scripts with, and it has different issues. Not sure how to post it as an attachment
Thank you in advance for the help :)
EDIT
I've been getting positive feedback for trying out BIML, and I will...but I want to know if in the short term anyone can help me figure out why this doesn't fill in ExternalMetaDataColumnId for my input. I've posted my updated code below with foreach loops that aren't doing what I expect them to.
Thank you
#region
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using System.Xml;
#endregion
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
#region Initial Setup
Application a = new Application();
Package p = new Package();
TaskHost t = p.Executables.Add("DTS.Pipeline") as TaskHost;
t.Name = "DataFlow Task";
t.Description = "Flat File to Database";
MainPipe mp = t.InnerObject as MainPipe;
#endregion
#region Flat File Source in Dataflow Task
IDTSComponentMetaData100 md = mp.ComponentMetaDataCollection.New();
md.ComponentClassID = "Microsoft.XmlSourceAdapter";
md.Name = "XML Source";
CManagedComponentWrapper wrp = md.Instantiate();
wrp.ProvideComponentProperties();
#endregion
#region Add connection manager to OLE DB
ConnectionManager conn = p.Connections.Add("OLEDB");
conn.Name = "westcoastuserDBO";
conn.ConnectionString = "Data Source=SERVER;Initial Catalog=DBO;Provider=SQLNCLI11.1;Integrated Security=SSPI;Auto Translate=False;";
#endregion
#region XML Source Properties
wrp.SetComponentProperty("XMLData", #"C:\Users\file.xml");
wrp.SetComponentProperty("XMLSchemaDefinition", #"C:\Users\file.xsd");
wrp.SetComponentProperty("AccessMode", 0);
wrp.SetComponentProperty("UseInlineSchema", false);
//below does not work
//wrp.SetComponentProperty("XMLIntegerMapping", 0).TypeConverter = "Microsoft.SqlServer.Dts.Pipeline.XmlSourceAdapter + XMLIntegerMappingConverter";
wrp.ReinitializeMetaData();
wrp.ReleaseConnections();
IDTSComponentMetaData100 md2 = mp.ComponentMetaDataCollection.New();
md2.ComponentClassID = "Microsoft.OLEDBDestination";
CManagedComponentWrapper wrp2 = md2.Instantiate();
wrp2.ProvideComponentProperties();
md2.Name = "OLE DB Connection";
md2.UsesDispositions = true;
md2.Version = 4;
wrp2.SetComponentProperty("OpenRowset", "dbo.authorizations");
#endregion
IDTSPath100 path = mp.PathCollection.New();
path.AttachPathAndPropagateNotifications(md.OutputCollection[0], md2.InputCollection[0]);
IDTSInput100 input = md2.InputCollection[0];
IDTSVirtualInput100 vInput = input.GetVirtualInput();
//below taken from https://stackoverflow.com/questions/12587709/c-sharp-ssis-data-flow-component-creating-custom-input-columns
IDTSExternalMetadataColumnCollection100 externalColumnCollection = input.ExternalMetadataColumnCollection;
// Iterate through the virtual input column collection.
foreach (IDTSVirtualInputColumn100 vColumn in vInput.VirtualInputColumnCollection)
{
// Call the SetUsageType method of the destination
// to add each available virtual input column as an input column.
wrp2.SetUsageType(
input.ID, vInput, vColumn.LineageID, DTSUsageType.UT_READONLY);
}
// Get the destination's default output collection
IDTSOutputCollection100 outColl = md2.OutputCollection;
// Iterate through the outputs in default output collection
foreach (IDTSOutput100 output in outColl)
{
// Iterate through the default output columns in the output
int count = output.OutputColumnCollection.Count;
foreach (IDTSOutputColumn100 outputColumn in output.OutputColumnCollection)
{
// Get the output's external metadata column collection
IDTSExternalMetadataColumnCollection100 extMetadataColumnColl = output.ExternalMetadataColumnCollection;
// Iterate through the external metadata column collection's external metadata columns
foreach (IDTSExternalMetadataColumn100 extMetadataColumn in extMetadataColumnColl)
{
// Call the MapOutPutColumn method of the destination to map
// each available output column to an external metadata column
wrp2.MapOutputColumn(
output.ID, outputColumn.ID, extMetadataColumn.ID, true);
}
}
}
md2.RuntimeConnectionCollection[0].ConnectionManager = DtsConvert.GetExtendedInterface(conn);
md2.RuntimeConnectionCollection[0].ConnectionManagerID = conn.ID;
conn.AcquireConnection(null);
#region Save Package to FileSystem
string packageXml = #"C:\Users\test.dtsx";
XmlDocument myPkgDocument = new XmlDocument();
p.SaveToXML(ref myPkgDocument, null, null);
a.SaveToXml(packageXml, p, null);
#endregion
}
}
}
I think the problem that you are not mapping the input columns to the OLEDB Destination, and after opening the package, if you click on the OLEDB Destination and go to the Mapping section, it will automatically map the columns based on their names. The Foreach loop that is used by others are to loop over columns and map them to the related Destination columns.
There are many articles talking about creating SSIS package dynamically, you can refer to them for more information:
Dynamic Data Flow in SSIS using .NET/C#
Programmatically map the columns of a flat file destination?
Building Packages Programmatically
Samples for creating SSIS packages programmatically
Generating SSIS Packages Programmatically (Part I)

Adding Multiple Stencils To Same Visio Sheet

I have C# code that creates a Visio Application instance, then opens some existing stencils so I can get the Shape Masters I need for my drawing. Visio 2013 changed things so I need 2 different stencils open. The issue is that I get 2 drawings open in Visio, 1 per stencil. When I build my document and save it, I can close it but there is still another empty drawing open. I also get an empty blank page in my active document where I am creating the drawing.
Visio.Application va = new Visio.Application();
va .Documents.Add(#"");
Visio.Documents vdocs = va.Documents;
const string templateNameU = "BASFLO_M.VSTX";
const string ConnectorStencilNameU = "BASFLO_M.VSSX";
const string RectangleStencilNameU = "BASIC_U.VSS";
const string stencilNameU = "CONNEC_U.VSSX";
const string connectorMasterNameU = "Dynamic Connector";
const string RectangleMasterNameU = "Rounded Rectangle";
Visio.Master connectorMaster = null;
Visio.Master rectangleMaster = null;
// open the templates we need
Visio.Document vc = vdocs.OpenEx(RectangleStencilNameU, short)Visio.VisOpenSaveArgs.visOpenDocked);
va.Documents.Add(templateNameU);
I have tried closing all the open drawings with:
foreach (Visio.Document d in va.Documents)
{
va.Documents[d.Name].Close();
}
va.ActiveDocument.Close();
but that is messy. The for loop for some reason doesn't close the active document. Is there a way to add multiple stencils to the same document and/or page so I am only working with one page? Is there a way to delete the blank page without resorting to a for loop to get the page name to delete it? I have looked through the API and don't see a way.
It looks like you are creating two documents.. i.e. the first (empty) one with va.Documents.Add("") and then the second one based on the template using va.Documents.Add(templateNameU).
If you don't want the first one, just don't create it.. Means, you can create new document, then open stencils, then draw, then close everything, like this:
var doc = va.Documents.Add(templateNameU)
var stn1 = va.Documents.Open(<first stencil>)
var stn2 = va.Documents.Open(<second stencil>)
// ... do stuff with the doc and then close everything...
doc.Close();
stn1.Close();
stn2.Close();
Am I missing something?
BTW, to get "Dynamic connector" you don't need to open the "Connector Stencil", it contains a specific dynamic connector. To get the default connector, you can just use Application.ConnectorToolDataObject
Also, you can connect shapes using shape.AutoConnect (this will also use the default connector)
Also, you don't need to open the stencil specifically actually. If it is part of the template, it will be opened automatically for you when you create a new drawing based on that template (so you can get them using Application.Documents[].
Maybe helpful? (draw 2 rectangles and connect them):
var doc = va.Documents.Add("BASICD_M.VSTX");
var stencil = va.Documents["BASIC_M.VSSX"];
var rectMaster = stencil.Masters["Rounded Rectangle"];
var rect1 = va.ActivePage.Drop(rectMaster, 1, 1);
var rect2 = va.ActivePage.Drop(rectMaster, 3, 1);
rect1.AutoConnect(rect2, Visio.VisAutoConnectDir.visAutoConnectDirNone);

C# Downloadable Excel Files from Class Library

I'm looking for some advice. I'm building on an additional feature to a C# project that someone else wrote. The solution of the project consists of an MVC web application, with a few class libraries.
What I'm editing is the sales reporting function. In the original build, a summary of the sales reports were generated on the web application. When the user generates the sales report, a Reporting class is called in one of the C# class libraries. I'm trying to make the sales reports downloadable in an excel file when the user selects a radio button.
Here is a snippet of code from the Reporting class:
public AdminSalesReport GetCompleteAdminSalesReport(AdminSalesReportRequest reportRequest)
{
AdminSalesReport report = new AdminSalesReport();
string dateRange = null;
List<ProductSale> productSales = GetFilteredListOfAdminProductSales(reportRequest, out dateRange);
report.DateRange = dateRange;
if (titleSales.Count > 0)
{
report.HasData = true;
report.Total = GetTotalAdminSales(productSales);
if (reportRequest.Type == AdminSalesReportRequest.AdminSalesReportType.Complete)
{
report.ProductSales = GetAdminProductSales(productSales);
report.CustomerSales = GetAdminCustomerSales(productSales);
report.ManufacturerSales = GetAdminManufacturerSales(productSales);
if (reportRequest.Download)
{
FileResult ExcelDownload = GetExcelDownload(productSales);
}
}
}
return report;
}
So as you can see, if reportRequest.Download == true, the class should start up the process of creating the excel file. All the GetAdminSales functions do it use linq queries to sort out the sales if they are being displayed on the webpage.
So I have added this along with the GetAdminSales functions:
private FileResult GetExcelDownload(List<TitleSale> titleSales)
{
CustomisedSalesReport CustSalesRep = new CustomisedSalesReport();
Stream SalesReport = CustSalesRep.GenerateCustomisedSalesStream(productSales);
return new FileStreamResult(SalesReport, "application/ms-excel")
{
FileDownloadName = "SalesReport" + DateTime.Now.ToString("MMMM d, yyy") + ".xls"
};
}
and to format the excel sheet, I'm using the NPOI library, and my formatter class is laid out like so:
public class CustomisedSalesReport
{
public Stream GenerateCustomisedSalesStream(List<ProductSale> productSales)
{
return GenerateCustomisedSalesFile(productSales);
}
private Stream GenerateCustomisedSalesFile(List<ProductSale> productSales)
{
MemoryStream ms = new MemoryStream();
HSSFWorkbook templateWorkbook = new HSSFWorkbook();
HSSFSheet sheet = templateWorkbook.CreateSheet("Sales Report");
HSSFRow dataRow = sheet.CreateRow(0);
HSSFCell cell = dataRow.CreateCell(0);
cell = dataRow.CreateCell(0);
cell.SetCellValue(DateTime.Now.ToString("MMMM yyyy") + " Sales Report");
dataRow = sheet.CreateRow(2);
string[] colHeaders = new string[] {
"Product Code",
"Product Name",
"Qty Sold",
"Earnings",
};
int colPosition = 0;
foreach (string colHeader in colHeaders)
{
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(colHeader);
}
int row = 4;
var adminTotalSales = GetAdminProductSales(productSales);
foreach (SummaryAdminProductSale t in adminTotalSales)
{
dataRow = sheet.CreateRow(row++);
colPosition = 0;
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.ProductCode);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.ProductName);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.QtySold);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.Total.ToString("0.00"));
}
}
templateWorkbook.Write(ms);
ms.Position = 0;
return ms;
}
Again like before, the GetAdminSales (GetAdminProductSales, etc) are contained in the bottom of the class, and are just linq queries to gather the data.
So when I run this, I don't get any obvious errors. The summary sales report appears on screen as normal but no excel document downloads. What I have done, which may be putting this off is in my class library I have referened the System.Web.Mvc dll in order to download the file (I have not done it any other way before - and after reading up on the net I got the impression I could use it in a class library).
When I debug through the code to get a closer picture of what's going on, everything seems to be working ok, all the right data is being captured but I found that from the very start - the MemoryStream ms = new Memory Stream declaration line in my formatter class shows up this (very hidden mind you) :
ReadTimeout '((System.IO.Stream)(ms)).ReadTimeout'
threw an exception of type
'System.InvalidOperationException' int
{System.InvalidOperationException}
+{"Timeouts are not supported on this stream."} System.SystemException
{System.InvalidOperationException}
I get the same for 'WriteTimeout'...
Apologies for the long windedness of the explaination. I'd appreciate it if anyone could point me in the right direction, either to solve my current issue, or an alternative way of making this work.
Without getting bogged down in the details, the obvious error is that in GenerateCustomisedSalesFile you create a MemoryStream ms, do nothing with it, then return it.

Categories

Resources