Featured Post

SQL Query in SharePoint

The "FullTextSqlQuery" object's constructor requires an object that has context but don't be fooled. This context will no...

Friday, September 9, 2011

Create a custom WCF NET.TCP service

I recently learned about creating a custom net.tcp service. I thought I'd make a nice step by step post on how to do this. Also, I'd just to note that this a standard WCF service example hosted in IIS 7.

Ok, so this is how to create a super simple net.tcp service

1.
Firstly, create a server application. In this example my server is a simple class library project. Now the only thing you'll need in this project are 4 files (see image). You will also need to add a reference to System.ServiceModel.











 2. UploaderService.svc 

This file needs to contain your servicehost tag. Set the "Service" value to that of your service class (including the namespace) and if you're deploying to the gac, set the assembly information otherwise. In this example I'm going to use CodeBehind. Here is an example of what your svc file should look like:


3. IUploaderService.cs

Add the ServiceContract attribute to your interface and make sure that all methods have the OperationContract attribute as well.

 4. UploaderService.svc.cs

This is your service class. Nothing fancy needs to happen here, just make sure your service implements your interface and you add all required methods. I added a Ping method that returns a simple boolean to help during setting up and debugging.



5. Web.Config

You can either apply settings to your service via code or via configuration settings. In this example I will demonstrate via the configuration settings. There are thousands of settings you can use but I am going to show you the most basic example you will ever need just to get your service working.


so you could literally copy this exact xml doc and just change the address for the service to wherever you plan on deploying it.

6. IIS Setup

So that's the server side code. To deploy the code, drop your svc, config and dll (in a bin folder) into a folder and add it as a virtual directory in a website. 


Last step for your service is to add the net.tcp as an enabled protocol and setup your bindings for your virtual directory. See images below for settings:


Now check whether your service is pingable via http.


7. Calling your service from the client application.

You can use one of the service utils to generate some client code to use in your client application but mine isn't working so I just copied the interface from my server to my client (if the interface ever changes you'd have to regenerate a new client file anyways so might as well just copy paste). I then use the interface to create a System.ServiceMode.ChannelFactory instance pointing to my service. Here is a code example:

Your client configuration will look similar to the server except that you won't need the service behaviour settings and instead, add the .net version type in the supportedRuntime setting.

 
Run your application and see if you can ping your service.

8. Debugging

If you receive any errors, you can add diagnostics to your config file that will give you more detailed information about the exception that is occurring between the client and the server. Add the following xml to your client application which will output a file in your bin which you can open using the svc tools.



9. Tips

So I made a very silly mistake this week. My web application was set to .NET 2 and my client was set to .NET 4. This obviously did not work so please make sure that the application pool that you are running your site under has the same .NET version as your client application.

Secondly my service's platform was set to 32 bit and my client's was 64 bit. This was also a problem so make sure that they are both built for the same platform.

10. The End

I hope this post helps you. If there is anything I might have missed please let me know and happy net.tcp.ing :)


SharePoint 2010 COM SaveBinaryDirect not reliable

I've recently found that the SharePoint 2010 client object model is not reliable. Calling the SaveBinaryDirect method was working fine for weeks until it started throwing errors. I looked up these errors and found no help. One of the errors I was receiving was "The remote server returned an error: (409) Conflict." Because I was unable to find any information about this error (that was relevant - there was no conflict), we decided to rather create a custom net.tcp service to handle the bulk document uploading.

Tuesday, August 16, 2011

Refreshing a SP.UI.ModalDialog's parent page

Basic javascript post around refreshing a modal dialog's parent page. For some reason I didn't find much help online even though such a common function. The dialog options have a property called "diaglogReturnValueCallback" where you can create a delegate callback to another function - remember that this function must exist on the same page. Then inside the Callback function I check whether the dialog result is "Ok" and then I call location.reload(true); Here's the code

//set the callback delegate
options.dialogReturnValueCallback = Function.createDelegate(null, CloseCallback);
                   

// the callback function
function CloseCallback(result, target)
{
  if (result == SP.UI.DialogResult.OK)
  {
    location.reload(true);
  }
}

Monday, May 30, 2011

Why does my image become smaller when inserting into Word?

I recently rewrote a report generation system which used to create word documents in Word (running on the server) to a new system which generates these reports in OpenXML. The report output from the new system had to completely match the output from the old system. One of the discrepencies we found was that some images that were being inserted were larger than those in the old system.

With a little investigation I found out that when inserting images into Word, it scales them down to 96 DPI (If larger). One specific image was 300 DPI so when inserting it using OpenXML is was MUCH larger than in the Word Generator output. I've been working with word for 7 years and I've never noticed or knew that this happened!!! Maybe I'm the only one but I thought this was rather interesting - hence this post.
So if you need your OpenXML code to insert your images the same way Word does here is some code to change the dpi of an image if its greater than 96:

Wednesday, April 13, 2011

using Application.ActiveDocument safely

In Word 2007 I would always check whether there were any document in my current application before I call Application.ActiveDocument. This is because if you call this method and there are no documents it would throw an exception.

Now developing in Word 2010, it only gets more complicated. There is now a ActiveProtectedViewWindow as well and this document cannot be retrieved in the applications document list.

So I created a helper method to get the current active document. Here's the code

Monday, March 7, 2011

Issues with AltChunk

I managed to find the underlying problem to a rather annoying bug using AltChunk. I've generated thousands of reports using OpenXml but this one word document would always break xml markup of the document when inserting it using AltChunk.

After stripping the document to only contain the parts that broke it I managed to find out that the "DocumentSettingsPart" contained some elements called SmartTagType. I've never seen these before and not sure what they are used for but the moment I removed them from the document my AltChunk insertion started to work so I now remove them from all my documents before I insert using AltChunk.

I wonder if this is a known bug - will ask on the forum.

Here's some code:

// Get a list of smart tags in the document settings part and remove these
List<SmartTagType> smartTags = mainDocumentPart.DocumentSettingsPart.Settings.Descendants<SmartTagType>();

// Loop backwards otherwise the elements orders change
for (int i = smartTags.Count - 1; i >= 0; i--)
{
  smartTags[i].Remove();
}

Friday, March 4, 2011

Maintain original image size when moving images in open xml

When inserting images into content control boxes using openxml, the image dimensions will not change to those of the image, you will need to manually do this.

Which objects?

There are two places where the image size needs to be set, inside the DocumentFormat.OpenXml.Drawing.Pictures.Picture object and the DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline object. Both of these objects contain an DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent object which contains the Cx and Cy values for the image.

What size?

Now the next problem is knowing what the Cx and Cy values should be set to. Use the System.Drawing.Image object to get the Width and Height properties from your image. However, word image dimensions are not stored in pixels, they are stored in points.

Converting pixels to points can be very complicated when you're working with text but luckily with images there is a constant value we can use to calculate the points from a pixel value, which is 9525.

Code example please?

Wednesday, February 16, 2011

Trouble with OpenXML and MemoryStreams?

I've been struggling for a while with changing my project to use Memory Streams instead of physical files. There were a few things I was doing wrong which you probably wouldn't pick up if you started off using physical files but because I was now refactoring I was missing a few pointers.

A) MainDocumentPart.Document.Save()

When working with the physical file, there is no need to save but when working with a memory stream you need to call this method just before you close your word document.

B) MemoryStream.WriteTo(fileStream)

Because I was working from a document that already existed I would open the document and not see any changes. This was because I was not saving the document back to the file. Very rookie, I know, but it happened and maybe this helps someone out there. More importantly see next point:

C) When to save your memory stream back to the file!!!!

I was writing my memory stream back to the file while the file was still open. This did not throw any exceptions and it still does not make sense to me why this is a problem but it is! Make sure that you always save the memory stream to a file after you have closed the word document but when your memory stream is still open. This caused me hours of pain :( If anybody knows why this happens please let me know.

Here is an example of what your code should look like

Tuesday, January 25, 2011

Moving images in word headers using OpenXML

From what I discovered moving images from one header in a document to another seems to be a huge problem for most people so I decided to try do this myself. I'm not 100% sure what other issues it might cause but I when I run the "Validate" command in the OpenXML Productivity Tools I get no errors and the images I'm moving are displaying correctly so for now I'm giving it the go ahead :)


Background


To move data from one document to another you can use altChunk but this does not copy headers and footers. If this doesn't phase you - you can read more about altChunk on Brian Jones blog: entry: http://blogs.msdn.com/b/brian_jones/archive/2008/12/08/the-easy-way-to-assemble-multiple-word-documents.aspx.


Brian Jones has tons of examples of how to manipulate documents. He also has some nice information about some open source code called "DocumentBuilder" which I stumbled upon but when I run the "Validate" command using the OpenXML Productivity Tools I get an error for each document I tried to merge so I decided this was not a good option for me. If you are interested though check it out here:  http://www.pubsub.com/events/5e5f8d3fd2346a54739b8b6bb0438b82


Another way to move elements from one part of a document to another is to actually use the "FeedData" method and pass in the other element by getting its Stream. This however doesn't work when moving images in headers between documents.

Looking Deeper




Create a blank document and insert any image into it's header. Use the OpenXML Productivity Tool to open the document and expand it's /word/document.xml node. You will see that there are few header xml nodes inside the document xml node. Thats because there are 3 types of headers inside a document or none at all. The three types distuinguish between First, Default and Even.


If you go to expand "w:document", "w:body" you will see "w:sectPr". If you reflect this node you will see the references to your headers. In my document my "Default" header has been referenced using id "rId8". If you reflect your "/word/document.xml node you should see which Header has been assigned "rId8". This helped me to find out which HeaderPart I need to retrieve to find the elements I'm looking for.


Start Coding


A HeaderPart can contain a few things but if I just have a plain header with an image it should contain a "/word/media/image1.png" element as well as a "w:hdr" element. What I did was create a new Header by passing the old headers OuterXML into the constructor and manually copy paste the Images seperately and this worked. Here is some example code:


reportImagePart.FeedData(headerImagePart.GetStream());

private static void ReplaceImages(HeaderPart headerPart, HeaderPart reportHeaderPart) 

{ 
  foreach (IdPartPair partPair in headerPart.Parts)   
  {
    OpenXmlPart openXmlPart = partPair.OpenXmlPart;   
    Type type = openXmlPart.GetType();   
    if (type.Name == "ImagePart")     
    {
      ImagePart headerImagePart = (ImagePart)openXmlPart;     
      string idImage = headerPart.GetIdOfPart(openXmlPart);     
      ImagePart reportImagePart = reportPart.AddNewPart<ImagePart>("image/png", idImage);
      Header headerNew = new Header(headerPart.Header.OuterXml);
    }
  }
}

This could probably be coded better but I'm still in the POC process. If anyone is having any issues with this strategy please let me know!!!

Thursday, January 6, 2011

Metadata Property Mappings

In order for me to make full use the FullTextSqlQuery SharePoint object which I spoke about in my previous blog here I needed to understand how metadata property mapping worked. This is my "lamens term" explanation: There are "Crawled Properties" which as a SQL developer you could think of as an indexed field. Then you get a "Managed Property" which you can map to multiple "Crawled Properties". The "Managed Property" is the field that will be available to use. You could think of this property as an interface to your indexed field(s).

So hopefully that doesn't make things more complicated, moving forward this is how you would go about creating Metadata Property Mapping:

To navigate to the Property Mapping Page go to Shared Services Provider. On the home page click on the "Search settings" option under "Search". The navigation menu should have changed now. Click on the "Metadata properties" item in the quick launch menu under "Queries and Results". On this screen you can see all the managed properties and their crawled property mappings. From this page you are able to see all meta data properties

If you navigate to the "Title" propery and click on it you will see the following screen:

Let me explain what all these properties mean:

A. The type of information in this property. The available types are (Text, Integer, Decimal, Date and Time, Yes/No). Note that you can only link crawled properties that are the same type as your managed property.

B. The number of items found with this property. This is a very useful indication of whether your managed property will actually return any results. If this value is 0 is means one of two things.
1. Your mapped crawled properties do not contain any values
2. You haven't run a crawl yet. After you have created a managed property always run a crawl in order for SharePoint to index it.

C. Option "Include values from all crawled properties mapped" means that all mapped crawled properties will be returned in a deliminated fashion.
Option "Include values from a single crawled property based on the order specified" means that it will only return the first value that is not null depending on their order in the list box.

D. Crawled properties mapping to this managed property.
On the title example you will see that the first mapped property is "Mail:5(Text)". The keyword "Mail:" means that this crawled property will be retrieved out of a mail object. This full name is not very descriptive to the user but you can just google a field or test it out to find out what value it contains.

If you click on the "Add Mapping" Button you will be able to search for your desired property. If you are looking for a column inside a list or library you will find them under the "SharePoint" category. All custom column's crawled property names will begin with "ows_" and spaces will be converted to "_x0020_". Note: if you cannot find the property you are looking for check whether they are of the same type as your managed property. This has got me before ;)

E. Allow this property to be used in scopes.
This boolean value can be misleading in it's description. If this checkbox is selected it means that you will be able to create a rule in your scope that can use this property. If this property is unchecked you can still use scopes in conjunction with this field in your searches.

So one last thing to remember is ALWAYS RUN A CRAWL after any updates!!!