I am developing a solution that will generate word-documents. The word-documents are generated on the basis of a template document which has defined content controls. Everything was working good for me when I had only one content control in my template, but after expanding the template document with more content controls, I am getting exceptions. It seems like I am not finding the content controls.
This is my method:
private void CreateReport(File file)
{
var byteArray = file.OpenBinary();
using (var mem = new MemoryStream())
{
mem.Write(byteArray, 0, byteArray.Length);
try
{
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
var firstName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "FirstName").Single();
var t = firstName.Descendants<Text>().Single();
t.Text = _firstName;
var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName").Single();
var t2= lastName.Descendants<Text>().Single();
t2.Text = _lastName;
mainPart.Document.Save();
SaveFileToSp(mem);
}
}
catch (FileFormatException)
{
}
}
}
This is the exception I get:
An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code. Innerexception: Null
Any tips for me on how I can write better method for finding controls?
I think your
Single()
method is causing the exception.When you got only one content control,
Single()
can get the only available element. But when you expand the content controls, yourSingle()
method can causeInvalidOperationException
as there are more than one element in the sequence. If this is the case, try to loop your code and take one element at a time.Your issue is that one (or more) of your calls to
Single()
is being called on a sequence that has more than one element. The documentation forSingle()
states (emphasis mine):In your code this can happen in one of two scenarios. The first is if you have more than one control with the same
Tag
value, for example you might have two controls in the document labelled "LastName" which would mean that this linewould return two elements.
The second is if your content control has more than one
Text
element in it in which case this linewould return multiple elements. For example if I create a control with the content "This is a test" I end up with XML which has 4
Text
elements:How to get round the first issue depends on whether you wish to replace all of the matching
Tag
elements or just one particular one (such as the first or last).If you want to replace just one you can change the call from
Single()
toFirst()
orLast()
for example but I guess you need to replace them all. In that case you need to loop around each matching element for each tag name you wish to replace.Removing the call to
Single()
will return anIEnumerable<SdtBlock>
which you can iterate around replacing each one:To get around the second problem is slightly more tricky. The easiest solution in my opinion is to remove all of the existing paragraphs from the content and then add a new one with the text you wish to output.
Breaking this out into a method probably makes sense as there's a lot of repeated code - something along these lines should do it:
Which can then be called from your code like so: