Showing posts with label visual studio 2008. Show all posts
Showing posts with label visual studio 2008. Show all posts

Wednesday, July 8, 2009

How to get around OpenXML error: Can not replace the OpenXmlElement "newChild" because it is part of a tree

I was recently working on a MS Word doc generation project and kept getting this error.

It took me a while to figure out the source of the problem, but it made total sense...
When you are a building a document tree (example below), you are creating new elements like


var myRun = new Run(new Text("reusable text"));

var myParagraph = new Paragraph();

myParagraph.AppendChild(myRun);



The 3rd line above ties in that Run object into the Paragraph object, so if you try to reuse the myRun object by appending it somewhere else - you will get this error.

I also tried creating a new Run() object using the old one as the object initializer:

var myRun2 = new Run(myRun);



That will throw the same error.
Why? Because the above line copies instantiates myRun2 with the reference of the myRun object so when you try to Append it elsewhere, you will get the error because it's technically the same object.

Workaround
There is a very handy property that most OpenXML elements have - OuterXml. The other handy thing about it - it's in most class constructors so it can be used to instantiate an object.
In lamens terms, instead of

var myRun2 = new Run(myRun);


try

var myRun2 = new Run(myRun.OuterXml);



Sample tree:
  • SdtBlock
  • - Paragraph
  • ----Run
  • ------Text
  • ----Run
  • ------Break
  • ----Run
  • ------Text

Sunday, February 22, 2009

OpenXML & Word Doc Manipulation using Sharepoint Data

Call me a lousy blogger but there is no whatisluxury.com code in this post because I've been busy refactoring it. That's not the only reason. I have been involved in a lot more Sharepoint development at work lately - moreso than .Net, so I've been kind of busy reading up SDKs and playing with APIs.

On that note, I got a chance to get my hands dirty with some OpenXML 2.0. The cool thing about that was that it was @ Microsoft. Not only did I get to play with it, but met the guys that developed parts of it and even wrote 2,000 of the 6,000 pages of documentation - Tristan Davis and Zeyad Rajabi - who helped me a great deal.

The story that I worked on involved gathering data from Sharepoint lists (some of it fed from the BDC(business data catalog)) and using OpenXML to populate a Word template. The real world scenario behind it is at my company, like I'm sure at a lot of other companies, we have administrative assistants who end up generating the documents for meetings. They just sit there copying and pasting data from various data sources and reports into this document. And there are as many documents generated as there are assistants multiplied by weeks in the year. Seriously - these people end up working 60 hour workweeks.

The program utilizing OpenXML to generate these documents for them is actually pretty simple. I had some issues deploying it as a web app onto Sharepoint because of various little issues that arose along the way (running sharepoint on a 64-bit windows 2008 server w/ IIS 7 - some assemblies were 32-bit, so couldn't get it to run; other times, there were IIS7 issues. Note to self: improve knowledge of IIS7). Because of that, I wrote it as a WinForms app.

The interface is pretty boring - it has 2 listboxes.
The first listbox contains key data - in this case, a list of product names. This field is used as a foreign key to relate it to a bunch of other lists - sales data list, product manager list, and even a Sharepoint picture library.
The second listbox contains a list of template documents from a document library that was created.
There is also a textbox for indicating what the new filename is going to be once the document is generated, and a button that generates the document.

So how does this all work?
First of all, none of this would be possible if Microsoft hadn't created the new .docx, .xlsx, .pptx formats in Office 2007. As much as I cursed it for doing so - I have seen the light last week. The new format is purely XML based. Hence, OpenXML allows us to manipulate the hell out of our documents without ever opening up Microsoft Word.

The one caveat is that Microsoft Word's Markup Language (WordML) is a bit cumbersome to learn right off the bat. The good news is that with a bit of blog reading from Brian Jones on OpenXML and an extremely powerful tool that comes with the SDK called DocumentReflector, you will be generating documents in no time. Here is a video by Alistair Spears that gives a demo of this wonderful tool: [http://video.msn.com/video.aspx/?vid=d50a0a13-836b-4849-9bbc-4c9134a63754&ifs=true&fr=msnvideo&mkt=en-US&from=writer]

So, how did I get data in from sharepoint lists into a word document? Let's take a look at the process step-by-step.

Traversing Sharepoint Lists/Doc Libraries/Picture Libraries
1. The first thing you will need is to connect to a sharepoint list. The code is the easy part - it's just 2 lines:

//declarations
protected SPListItemCollection product_owner_items;
protected SPListItemCollection template_doc_items;
protected SPListItemCollection marketing_info_items;
protected SPPictureLibrary product_model_pics;
protected SPSite mysite;
protected SPWeb myweb;
mysite = new SPSite("http://izmqdp6452022/Docs/Lists/Product%20Owners/AllItems.aspx");
myweb = mysite.AllWebs["Docs"];

SPSite's constructor can actually determine what site you are connecting to in your Sharepoint farm based on the URL passed in. You just have to make sure that whichever user your app is running as, must have access to these libraries and to the Sharepoint site.

Here is the code used to get my Sharepoint list objects ready for access:
private void initialize_sharepoint_lists()
{
  product_owner_items = mysite.AllWebs["Docs"].Lists["Product Owners"].Items;
  marketing_info_items = mysite.AllWebs["Docs"].Lists["Marketing Info List"].Items;
  template_doc_items = mysite.AllWebs["Docs"].Lists["Documents"].Items;
  product_model_pics = (SPPictureLibrary)mysite.AllWebs["Docs"].Lists["Product Model Images"];
}
In this case, "Docs" is the name of the sharepoint site and the Lists indexers contain the names of the libraries that I am working with in sharepoint.

Once I have the data I need to be inserted into the Word document, I need to create a template in Microsoft Word. This is the easiest part of the operation. You simply open up Microsoft Word, enable the Developer toolbar by right-clicking on the ribbon and insert a text-box field wherever you will have data inserted. Make sure you assign an alias to the text-box - this is how you will access your placeholder for insertion.

Before attempting this exercise, you should find a sample Word 2007 document and open it in DocumentReflector so that you can get an idea of how the XML is formatted because the code below can get confusing REAL quickly if you don't.

The fields that you created using the developer tab can be accessed one of two ways. If the field is on a new line, it will live in a <SdtContentBlock> element. If the field is on the same line as some text, say, a label, it will live in a element. Now, the trick is to locate these elements and pass the data into them.

Here is the method that will take a string, the alias of the field you are manipulating, and a WordProcessingDocument object which contains the open word document. This method will modify a SdtRun block - the element that lives on the same line as other text:

 private void populate_run_control(WordprocessingDocument wordDoc,string alias, string value)
        {
            var contentControls = wordDoc.MainDocumentPart
                            .Document
                            .Descendants()
                            .Where(s => s.SdtProperties.GetFirstChild().Val.Value == alias);
 
            if (contentControls.Count() != 0)
            {
                var sdt = new SdtContentRun(
                           new Run(
                                new RunProperties(
                                    new RunStyleId() { Val = "PlaceholderText" }),
                                new Text(value)));
 
                contentControls.First().SdtContentRun.RemoveAllChildren();
                contentControls.First().SdtContentRun.InsertAt(sdt, 0);
 
            } 
        }

The method below has the same task in mind, but modifies an SdtContentBlock element that starts on a new line. Another difference is that it takes in an html Table object, which can be easily formed just like you are used to in ASP.Net/C# - Table, TableRow [tr], TableCell, etc. This could be as simplistic or as complex as you want.

        private void populate_block_table(WordprocessingDocument wordDoc, string alias, Table value)
        {
            var contentControls = wordDoc.MainDocumentPart
                            .Document
                            .Descendants()
                            .Where(s => s.SdtProperties.GetFirstChild().Val.Value == alias);
 
            if (contentControls.Count() != 0)
            {
                var sdt = new SdtContentBlock(
                           value);
 
                contentControls.First().SdtContentBlock.RemoveAllChildren();
                contentControls.First().SdtContentBlock.InsertAt(sdt, 0);
 
            }
        }

I won't bore you with the rest of the code used to open files into MemoryStreams or save them, but if you would like to see the inner workings anyway, I recommend taking a look at Brian Jones's blog post about Office Extensibility: [http://blogs.msdn.com/brian_jones/archive/2009/01/19/pushing-data-from-a-database-into-a-word-document.aspx]

Don't hesitate to comment and let me know if you have any questions.


Thursday, September 18, 2008

Dynamic Drop-Down Menus in ASP.Net 2.0 / 3.5

One of the most essential components to a solid site is a versatile and intuitive navigational drop-down menu. There are many ways to do this. My goals, as always, when approaching a challenge:

  1. Let's not reinvent the wheel.
    There are many solutions out there. A big point of the .NET Framework is to provide reusable components so that you don't have to code things from scratch. Chances are it's already been developed and developed well.

  2. Avoid hard-coding where possible.
    It's all HTML, CSS, and Javascript by the time the menu renders in your browser, but that doesn't mean you need to code it in that way. We're trying to build something dynamic and avoid duplicating code from page to page. That means there is going to be a data feed, whether from XML or a database, and a control that can process and display this feed from page to page.

  3. Create an elegant, cross-browser compliant solution.
    Some of the out-of-the-box ASP.Net menu controls are not that pretty and don't really work across all browser platforms, especially Safari (Webkit based... speaking of which - guess what Google's Chrome uses ;-) ) We need something that will be readable, easy to maintain, and most importantly, work across all browsers.
I found a couple of methods out there to accomplish this. Peter Bromberg talks about one way in his blog here (http://www.eggheadcafe.com/tutorials/aspnet/f4bf9b1a-c934-43be-b026-9f3443fc87c9/build-an-aspnet-20--xml.aspx).

It's a variation of an ASP.Net 1.1 solution previously published in 2003 by WROX. It works, but it's a slight modification from the 1.1 version, and it seemed a little cumbersome to customize the look and feel, so I continued to look for something designed specifically for 2.0 and up that would be simple to use out-of-the-box and easy to customize.

Surely enough, I stumbled upon a solution that met my three goals, written by ScottGu (http://weblogs.asp.net/scottgu/archive/2006/05/02/444850.aspx)

To build a drop-down menu, we need the following:

  1. Data Feed
  2. CSS Stylesheet
  3. Server-side control that will generate a menu from #1 and #2.
The data feed is your standard web.sitemap (http://msdn.microsoft.com/en-us/library/system.web.sitemap.aspx) that you can build in Visual Studio that looks something like this:


<xml version="1.0" encoding="utf-8" ?>

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >

<siteMapNode url="" title="" description="">

<siteMapNode url="" title="" description="" />

<siteMapNode url="" title="" description="" />

</siteMapNode>

</siteMap>


The server side control is our typical with a SiteMapProvider, which is just responsible for reading in the data feed - in this case our web.sitemap file.

The CSS is the tricky part. Using out-of-the-box simplified customizations won't get you your desired result, and if it will, chances are it probably won't look the same from browser to browser. Our solution:

CSS Control Adapter Toolkit for ASP.Net 2.0
You can download it here: http://www.codeplex.com/cssfriendly

The toolkit essentially takes the worry out of coding for cross-browser compliance by providing ASP.Net controls that are already formatted in CSS to look pretty and the same in all common browsers, which is fantastic, because usually, you have to buy a component like this.

For our drop-down menu, you can do vertical or horizontal; it uses CSS best practices by utilizing ul/li's instead of tables.

Check out ScottGu's blog to get the code, documentation, and a more detailed explanation of exactly how to build, install, and use the drop-down menu with the CSS Control Adapter Toolkit.

As always, I welcome your comments.

UPDATE - 9/22/08
As it turns out, the vertical menu doesn't display the sub-items properly in IE6. That doesn't work for me as more than 45% of my user base is on IE6. Stay tuned and I will post a fix for it.


Applies to:


Wednesday, September 10, 2008

Making use of the 'Drop" in Drag-and-Drop in ASP.NET/AJAX

I find it a lot when developers get asked what is AJAX, a very common answer is - it's that technology that let's you drag and drop things without leaving the page.

Working within the .NET 2.0 or the 3.5 framework, there is functionality in the AJAX toolkit that let's you drag things using the DragPanel control in the AJAX control toolkit (http://www.asp.net/AJAX/AjaxControlToolkit/Samples/DragPanel/DragPanel.aspx). Very cool stuff if all you are trying to do is allow the user to reposition things. If you are trying to extend it for a more practical use, like being able to drag an item to a shopping cart, you don't really get that functionality out of the box with the AJAX control toolkit.

Looking at our objective from a 64,000 ft. view, we have this "hot" zone somewhere on a page. We have a couple of objects that can be dragged onto this zone, and when that happens, we want to be notified exactly what was dragged and to which zone, so that we can fire the appropriate code, such as add the object to our shopping cart.

How do we accomplish this given that this functionality is non-existent in the AJAX control toolkit? I was asking myself this question until I ran across a very cool solution by Jeff Prosise who writes for a section in the MSDN Magazine called Wicked Code. You can subscribe to it here: http://msdn.microsoft.com/en-us/magazine/rss/?col=Wicked%20Code

The caveat with his solution is that it uses the Microsoft.Web.Preview library which comes as part of ASP.Net Futures (downloadable from here - http://www.microsoft.com/downloads/details.aspx?FamilyId=A5189BCB-EF81-4C12-9733-E294D13A58E6&displaylang=en). The official description of the ASP.Net Futures is "Contains features currently being considered for future versions of ASP.NET and the .NET Framework," meaning you probably won't find too much documentation or support for it... or a guarantee of any sort that it will be included in the next release of the .NET Framework.

There is the fine print. If you are planning on deploying this to a high volume enterprise environment, it's certainly something to think about, however, Jeff's solution looks elegant, robust, and customizable to fit your need.

The code, article, and documentation is available here:

Great work Jeff!
Applies to:

Friday, September 5, 2008

Workaround to AuthenticationService security issue over HTTP

It's very convenient to be able to log in on a nice & functional page without leaving to go to a special secure & encrypted login page, hoping to be brought back to the page that you were on with all your work saved.

The AJAX Framework in NET 3.5 provides a web service to help us accomplish that: AuthenticationService (http://msdn.microsoft.com/en-us/library/bb386582.aspx).

If you are already on an encrypted HTTPS page, this works great. However, if you are serving out that nice & functional page over HTTP, because frankly, you don't need images or text encrypted, and would rather not use up overhead just for the sake of a login form, you may have a problem.

Specifying the line below in your web.config won't really do anything.
<authenticationservice enabled="true" requiressl="true"></authenticationservice>

Changing the protocol to HTTPS in your path won't help you either:
<asp:scriptmanager runat="server">
<authenticationservice path="https://localhost/MyAuthService.asmx"></authenticationservice></asp:scriptmanager>

Why? Because it so happens that this version of AJAX, included in .NET 3.5 does not support cross-domain proxy authentication.

So, how do you solve the problem of making sure your user's credentials don't get traced through unencrypted HTTP headers while keeping the page served over HTTP and not redirecting him/her anywhere?

MD5 encryption via Javascript

Before the password gets posted to your ASP.Net form, you encrypt it with MD5. MD5 is a one-way encryption algorithm that cannot be decrypted. To use it, you:
  1. Encrypt the submitted password value.
  2. Encrypt the DB-stored password associated with the submitted email address.
  3. Compare the two values. If equal, you can deem the authentication successful.

With no need to reinvent the wheel, I found a great script for encrypting strings into MD5 format here:
http://pajhome.org.uk/crypt/md5/
Great work Paul!

Conclusion
With the shortcomings of the AJAX toolkit inside of .NET 3.5 Framework, Visual Studio 2008 is still a great IDE and before you go switching to PHP, all it takes is some time, creativity, and improvisation.

Is it safe to use client-side encryption?
Yes. Our goal is to prevent the HTTP headers from being compromised or traced in midstream, before they get to the server they are going to. As long as you are on a browser that supports JavaScript, Paul's MD5 function should do the trick.

I will post some code later. I welcome your feedback.



Applies to:
, ,