November 2008 - Posts
Voow, I am almost done. On this Sunday night, at Panera, I have enjoyed writing while listening to the jazz and drinking coups of tea (3, so far).
Alright, let's summarize briefly what we have done so far in these series: After giving some intro, In this complete but simple web application, we import data from an excel spreadsheet uploaded by the user into a database table by leveraging .NET 3.5 framework and ExcelPackage with C#.
Plus, we expose to some other areas of application development:
- Best practices and Enterprise Library (used Data and Logging Blocks). Later on, I will deep into more details hopefully
- Object serialization and de-serializaition
- File operations using System.IO namespace (FileInfo, Stream/FileStream)
- Resource releases/disposal once it is done by using 'using' blocks
- XML Document
- Use of XPATH for accessing XML content.
- Use XML Document as way of importing data (like a Batch process)
- T-SQL Script for reading data from an XML Document
- Rendering data as an Office document
- Use of Data Controls.
I will also publish the solution. Please note that this is just a sample application, I have used very simple development architecture (UI and Business), it is not optimized nor tested well.
I think I am done. Bye for now & All the Best!.
Hello everyone,
This is my 'Hello World' to the Blog world. Here is my short introduction: Almost a decade of experience on mainly web-based, distributed, object oriented applications (front and back end) using .NET Framework, C#, ASP.NET, UML, Java, and XML with BizTalk, Oracle and SQL Server databases with roles of an architect, a lead developer, or a developer/programmer. Exposed to different platforms (.NET, J2EE), but dive into ASP.NET applications more lately.
Life itself is a learning experience, so be IT. And here I am, I think it is time to share my experiences/thoughts with the world and to be more active within the community.
Thanks to Joe Stagner, and Jeffrey Blankenburg for setting up this blog for me.
Wish you well.
In order to display data generated in prior calls as an Excel file on the user browser, we need to set 2 properties of the Response object (in its simplest form):
- ContentType to 'application/vnd.ms-excel'
- EnableViewState to 'False'
Then with the header information, we send the data into the client.
/// <summary>
/// Displays data (if exist) as an Excel spreadsheet on user browser.
/// </summary>
/// <param name="bytes">The bytes to be the content of the excel spreadsheet</param>
private void DisplayNewExcelFile(byte[] bytes)
{
if (bytes == null || bytes.Length == 0)
{
litMsg.Text = "No data found.";
return;
}
Response.ClearHeaders();
Response.ContentType = "application/vnd.ms-excel";
Page.EnableViewState = false;
Response.AddHeader("content-disposition", "attachment;filename=PersonAddress_Modified.xlsx");
Response.BinaryWrite(bytes);
try
{
Response.End();
//this causes a System.Threading.ThreadAbortException, which ends the current request.
}
catch { }
}
|
We are close to the end of these series. The procedure taking the XML Document as an input parameter, simply look for the nodes in the 'Root/Data' path then using its aliases inserts its values into PersonAddress table. Finally returns the recordsets, just inserted, in its simplest form. Here, we could probably return data as XML document as well.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[ImportPersonAddressXml](
@XmlDoc xml
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @CurrentDt datetime;
SET @CurrentDt = getDate();
BEGIN TRANSACTION
INSERT INTO Person.Address(AddressLine1, AddressLine2, City, StateProvinceID,
PostalCode, rowguid, ModifiedDate)
SELECT
P.e.value('@AddressLine1','nvarchar(60)'),
P.e.value('@AddressLine2','nvarchar(60)'),
P.e.value('@City','nvarchar(30)'),
P.e.value('@StateProvinceID','int'),
P.e.value('@PostalCode','nvarchar(15)'),
P.e.value('@RowGuid','uniqueIdentifier'),
@CurrentDt
FROM @XmlDoc.nodes('/Root/Data') P(e)
COMMIT TRANSACTION
SELECT * FROM Person.Address WHERE ModifiedDate = @CurrentDt
END
|
In the previous blog (Office Integration -3), we serialized the excel spreadsheet into a stream object then open a Package on it. Now, according to our scenario, it is time to do some validation. Since I prefer doing validation on the objects rather than the Package directly, first we de-serialize the stream to the PersonAddress typed objects. Again according to plan; the validation checks:
- if column numbers matches
- if required data exist.
Yes, I agree the validation would be complex in the real world application, however here we focus on Excel package rather Validation. Once the validation passes for all the records, we then serialize the PersonAddress objects back into an XML document. At the end, we will push the XML doc into database stored proc, which will execute importing and returning the records with updated fields (AddressID and ModifiedDate).
/// <summary>
/// Transforms the Excel Worksheet into XML document.
/// </summary>
private void TranslateXlToXmlDocument()
{
int rowCurrent = 1; //excel row index
bool flgContinue = true;
XmlElement root = _xmlDoc.CreateElement(_rootNodeName); //creates root element
_rowCount = Convert.ToInt32(_xlWS.WorksheetXml.SelectSingleNode("//d:sheetData/d:row[last()]",
_xlWS.NameSpaceManager).Attributes.GetNamedItem("r").Value);
//For each data row in the excel worksheet, generate a PersonAddress
//Then validate the object against to business rules
//If fails, then keep running validation check for the rest and omit creating xml document
//If succeeds, convert the objet into xml element having attributes for each object property then append it.
while (++rowCurrent <= _rowCount)
{
//create PersonAddress object from the xml document
PersonAddress personAddress = TranslateXlRowToEntity(rowCurrent);//ws index starts with 1 and plus header
ValidateEntity(personAddress, rowCurrent);
if (flgContinue && _errors.Count > 0)
flgContinue = false;
if (flgContinue)
{
XmlNode node = ImportUtility.ConvertToXml<PersonAddress>(personAddress, _xmlDoc, _childNodeName);
if (node != null)
root.AppendChild(node);
}
}
if (flgContinue)
{
_xmlDoc.AppendChild(root);
//#if DEBUG
#region testing Xml
string strPath = System.Web.Hosting.HostingEnvironment.MapPath("~") + @"\App_Data";
if (!Directory.Exists(strPath))
Directory.CreateDirectory(strPath);
string path = Path.Combine(strPath, "ExcelToXml.xml");
if (File.Exists(path))
File.Delete(path);
_xmlDoc.Save(path);
#endregion
//#endif
}
}
|
/// <summary>
/// Returns the XML Node generated from the object with T type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t">the Object</param>
/// <param name="doc">The XML Document</param>
/// <param name="nodeName">The Name for the Xml Node </param>
/// <returns></returns>
public static XmlNode ConvertToXml<T>(T t, XmlDocument doc, string nodeName)
{
XmlNode node = doc.CreateElement(nodeName == null ? t.GetType().Name : nodeName);
PropertyInfo[] properties = t.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
XmlAttribute attr = doc.CreateAttribute(property.Name);
attr.Value = (property.GetValue(t, null) != null) ? property.GetValue(t, null).ToString() : "";
node.Attributes.Append(attr);
}
return node;
}
|
So What is ExcelPackage really?
In short, it is to create and/or modify Excel spreadsheets from the server. To me, it is an open source API that includes classes and wrappers around .NET 3.0 System.IO.Packaging class library according to new Open XML standards. It is called Package because it contains multiple data objects, Worksheets, Worksheet, Row, Cell, Column, Styles, etc... A good example would be Zip file. It is aimed to be a complete solution, however it is still in progress, applying styles, rows cloning, etc waiting for heroes... -Hey it is open source... Thanks Dr. John Tunnicliffe, for his generous contribution.
How does it work?
If you look at the System.IO.Packaging namespace, you will see there are different ways of opening a Package. And I am particularly talking about one of them -opening a Package from a stream object:
Steps:
- Dump the excel data into a FileStream typed object,
- Open a Package object on the stream object,
- Construct an Excel worksheet in the package at given index.
- Now, we can start all kinds of CRUD (Create, Read, Update, Delete) operations on the worksheet for its rows, columns, cells, styles, properties. Easy right:)
/// <summary>
/// Imports Excel spreadsheet into Database
/// </summary>
/// <param name="results">The list of errors if any</param>
/// <returns>Array of bytes of after excel saved</returns>
public byte[] ImportExcelData(out List<string> results)
{
DataTable dt;
_xmlDoc = new XmlDocument();
_errors = new List<string>();
_bytes = File.ReadAllBytes(_fileName);
if (_bytes == null || _bytes.Length == 0)
throw new Exception("No data found in the file. ");
FileInfo fi = new FileInfo(_fileName);
//writes data into filestream object
using (FileStream fs = fi.OpenWrite())
{
fs.Write(_bytes, 0, _bytes.Length);
fs.Flush();
}
//create ExcelPackage with fileinfo object created above
using (_xlPackage = new ExcelPackage(fi))
{
//initialize the excel worksheet object from the excel package at given index.
_xlWS = _xlPackage.Workbook.Worksheets[_sheetIndex];
if (_xlWS == null || !_xlWS.WorksheetXml.HasChildNodes)
throw new Exception("No worksheet found at given index.");
//transforms ExcelWorksheet context to Xml document.
TranslateXlToXmlDocument();
if (_errors.Count == 0)
{
//inserts XML document into database table
dt = InsertXmlData();
//syncs the excel worksheet with database data
UpdateXlWS(dt);
//accepts changes made to excel worksheet --> excel package.
_xlPackage.Save();
}
}
fi.Refresh();
byte[] updatedData = (_errors.Count == 0) ? File.ReadAllBytes(fi.FullName) : null;
results = _errors;
return updatedData;
} |
More Posts