Wednesday, July 30, 2008

BDC to query a WebService using LINQ

In this post I'll describe how to connect your BDC definition to an external WebService. I'm assuming that you are using Visual Studio 2008, a SQL 2005 database hosting the Northwind database on the local server, MOSS 2007 and you've got the SharePoint SDK installed. Also this is a quick demo so I'll be connecting to debug instance of the webservice and not creating a permanent site as that's not really the point of this blog.

Stage 1: Building the WebService



Open Visual Studio 2008 and create a new WebSite Project of type "ASP.NET Web Service" called "Store". It should be noted at this stage that WCF is not fully supported in BDC although basicHTTP protocol is allowed. This will create a standard Service.asmx file with a default HelloWorld method.


Add a LINQ connection to your database using "Tools/Connect to Database" setting the server name to "(local)", Log on to the server as "Use Windows Authentication" and Database Name as "NORTHWIND". Test that the connection is working fine and Click OK.


Within your project Visual Studio, right click the Project file and select "Add New Item" and select a "Linq to SQL Classes" called Products.dbml. If you get the message regarding placing the file within the APP_Code folder, click Yes.


Within Visual Studio drag the "Product" table from the ServerExplorer to the Products design window. This should display a list of all fields in the table. This is DBML file contains all the code needed to connect to the database and query the table. Save the file and close the designer window.


Now we need to create a simple structure to pass this information back over the HTTP protocol. We do this by building a class containing only those fields we want to pass. Within your project Visual Studio, right click the Project file and select "Add New Item" and select a "Class" called ProductProxy.cs. If you get the message regarding placing the file within the APP_Code folder, click Yes. Replace the default code with the following:

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

/// <summary>
/// Summary description for Product
/// </summary>
public class ProductProxy
{
private int productIDField;
private string productNameField;
private decimal unitPriceField;
private double unitSalesPriceField;
private int unitsInStockField;

/// <remarks/>
public int ProductID {
get {
return this.productIDField;
}
set {
this.productIDField = value;
}
}

/// <remarks/>
public string ProductName {
get {
return this.productNameField;
}
set {
this.productNameField = value;
}
}
/// <remarks/>
public decimal UnitPrice
{
get
{
return this.unitPriceField;
}
set
{
this.unitPriceField = value;
}
}

/// <remarks/>
public int UnitsInStock
{
get
{
return this.unitsInStockField;
}
set
{
this.unitsInStockField = value;
}
}

/// <remarks/>
public double UnitSalePrice
{
get
{
return this.unitSalesPriceField;
}
set
{
this.unitSalesPriceField = value;
}
}

}

Now we create the web method that connects everything together. Open the Service.cs file and replace the existing code with the following:

using System;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service () {}

[WebMethod]
public ProductProxy[] ProductList()
{
ProductDataContext db = new ProductDataContext();
var query = from p in db.Products
select p;

ProductProxy[] prods = new ProductProxy[query.Count()];
int record = 0;

foreach (var q in query)
{
ProductProxy prod = new ProductProxy();
prod.ProductID = q.ProductID;
prod.ProductName = q.ProductName;
prod.UnitPrice = q.UnitPrice.Value;
prod.UnitsInStock = q.UnitsInStock.Value;
prod.UnitSalePrice = (double)q.UnitPrice.Value * 1.10;
prods[record] = prod;
record++ ;
}

return prods;
}

}

At this point we test the WebMethod by running the application in Visual Studio 2008 debug mode. Choose Debug/Start Debugging, if you get a message about modifying the web.config to allow debugging select "OK" or that ScriptDebugging in InternetExporer select "Yes". Once the HTML page appears, click the Service.asmx link, note the URL displayed as we'll use this later, then click the ProductList link and then the "Invoke" button. The results of the query should be displayed on screen in XML format.



Stage 2: Developing the BDC Mapping


Start the Business Data Catalog Definition Editor which is available when you install the SharePoint SDK. From the application tool bar click the "Add LOB System" then click the "Connect to Webservice" and type the URL of the debug instance noted previously (in my case it was "http://localhost:3534/Store/Service.asmx", however your port number could be different. Clicking Connect should present you with a blank deisng surface with a toolbar on the right hand side of the screen. From this toolbar click "Add Web Method" and drag the "ProductList" method onto the design surface. Finally click the OK button and choose the standard "WebServiceLobSystem" as the name and click OK.



We need to create a Method Instance in order to test that or generated mapping is operating correctly. Browse to the Entities/Entity0/Methods/ProductList/Instances node and click "Add Method Instance" on the toolbar. Choose "Finder" as the Method Instance Type and click OK. While the MethodInstance0 is selected choose "Execute" from the tool bar and click the "Execute" button from the dialogue box displayed, should show a set of results. Then close the window.


Select the top node "WebServiceLobSystem" on teh left hand side of the screen. Click "Export" from the tool bar and save the XML file to your desktop as "BDC_WS_Example".

Stage 3: Importing to SharePoint


Open SharePoint 3.0 Central Administration from the Administrative Tools folder on your sever. Browse to the Share Services Administration window and select the SharedServices that runs your site, in my case it was the default SharedService1. Then select the "Import application definition" link.

Click "Browse" and navigate to the BDC xml file we created in the previous stage and click "Import". All going well you should be told that the application was imported successfully. You will see a warning message saying "No method instance of type SpecificFinder defined for for application 'WebServiceLobSystem', entity 'Entity0'. Profile page creation skipped", which is just saying that we have not defined a method, but for it will not effect this demo. Just click OK and you should see the application details shown on screen. You are now ready to implement the BDC object inside your SharePoint site.

Browse to your SharePoint website, in my case http://sharepoint1/Pages/Default.aspx, and create a new "Blank Web Part Page" called Products. Add a new "Business Data List" webpart In any of holding areas on the page.


Open the tool pane by clicking on the link within the new webpart. Browse to the Type you wish by clicking the book icon shown. Select the "WebServiceLobSystem_Instance" which is the name provided in the previous stage and click OK. Then click OK on the SharePoint WebPart tool bar shown on the right of teh screen.


At this point the details can be seen on screen.


Now you have a working demo feel free to play around with the various settings and options within SharePoint and the BDC Editor.

The End.

Sunday, June 15, 2008

Ghostdoc

Had a very nice developer tell me about an automated documention tool called Ghost Doc. Being a sceptical sole I doubted it would live up to the hype, however after a few minutes I was hooked.

Easy to download and install, assign a quick key and away you go. It defaults to D, which I kept .. yes I was that sceptical !

Roland Weigelt's G h o s t D o c (http://www.roland-weigelt.de/ghostdoc/) gets five gold stars from me.

Thursday, April 24, 2008

Error with SharePoint BDC

I recently got the error :
"Application definition import failed. The following error occurred: The IdentifierName Attribute of the TypeDescriptor named 'FirstName' of the Parameter named '@FirstName' of the Method named 'GetCustomers' of the Entity named 'Customers' does not match any of the defined Identifiers for the Entity. Error was encountered at or just before Line: '57' and Position: '16'."
When trying to import a BDC schema into MOSS 2007.

It turned out that I'd forgotten to add the following line to the <Identifiers>
<Identifier Name="FirstName" TypeName="System.String" />

Following that fix I get the next error:
"Application definition import failed. The following error occurred: The AssociatedFilter Attribute of the TypeDescriptor named 'FirstdName' of the Parameter named '@FirstName' of the Method named 'GetCustomers' of the Entity named 'Customers' does not match any of the defined FilterDescriptors for the Method. Error was encountered at or just before Line: '57' and Position: '16'."

So I added the lines to the <FilterDescriptors> tag:
<FilterDescriptor Type="Wildcard" Name="FirstName" >
<Properties>
<Property Name="UsedForDisambiguation" Type="System.Boolean">true</Property>
</Properties>
</FilterDescriptor>

Following that, I got another error after successful installation of the schema into SharePoint when the User attempted to run the webpart. "The Business Data Catalog is configured incorrectly. Administrators, see the server log for more information.". That error is found within the Servers Event Log:
"A Metadata Exception was constructed in App Domain '/LM/W3SVC/1959965621/Root-2-128667808042048704'. The full exception text is: Could not find appropriate places on the root TypeDescriptor to read sub-identifier values for the Instance of Entity 'Customers'"

The fix for this was because I forgot to add the "IdentifierName="FirstName" to the TypeDescriptor tag.
<TypeDescriptor TypeName="System.String" IdentifierName="FirstName" Name="FirstName" >
<LocalizedDisplayNames>
<LocalizedDisplayName LCID="1033">FirstName</LocalizedDisplayName>
</LocalizedDisplayNames>
<Properties>
<Property Name="DisplayByDefault" Type="System.Boolean">true</Property>
</Properties>
</TypeDescriptor>

So the rule of the game when using the SharePoint BDC is watch your XML when adding new fields to a query..

Tuesday, April 15, 2008

Double Metaphone

Found a really nice search function which allows for "Sounds like" searchs in C#. It's called the Double Metaphone (http://en.wikipedia.org/wiki/Double_Metaphone
) which breaks the name down into a small 4 char fild which you can match.

The source code can be found from the CodeProject website (http://www.codeproject.com/KB/recipes/dmetaphone5.aspx). simply include this in you VS2005 project (it should convert without any issues).

using using nullpointer.Metaphone;
....
DoubleMetaphone mphone = new DoubleMetaphone(_surname);
string mphone_Surname = mphone.PrimaryKey;
....

Monday, April 7, 2008

WCF and BizTalk

Had a major problem with joining BizTalk to a WCF Server application today. There seemed to be very little information out there on the net regarding the very generic error I found in the Event Log:

Event Type: Warning
Event Source: BizTalk Server 2006
Event Category: BizTalk Server 2006
Event ID: 5743
Date: 7/04/2008
Time: 9:55:14 PM
User: N/A
Computer: IFUBUY
Description:
The adapter failed to transmit message going to send port "WcfSendPort_Service_WSHttpBinding_IService" with URL "http://localhost:1501/WCFService1/Service.svc". It will be retransmitted after the retry interval specified for this Send Port. Details:"System.ServiceModel.Security.MessageSecurityException: An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail. ---> System.ServiceModel.FaultException: The message could not be processed because the action '' is invalid or unrecognized.
--- End of inner exception stack trace ---

Server stack trace:
at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndRequest(IAsyncResult result)

Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System.ServiceModel.Channels.IRequestChannel.EndRequest(IAsyncResult result)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.RequestCallback(IAsyncResult result)".

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

After some time I found that the default bindings generated by the WCF BizTalk Wizard was incorrect (or at least not correct for a single test purpose).
<BtsActionMapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Operation Name="GetData" Action="http://tempuri.org/IService/GetData" />
<Operation Name="GetDataUsingDataContract" Action="http://tempuri.org/IService/GetDataUsingDataContract" />
</BtsActionMapping>

Should be replaced with simply
http://tempuri.org/IService/GetData

Now I've to figure out how to make it dynamic!

Saturday, April 5, 2008

WCF and WPF

Started investigations into the wonderful world of WCF and WPF today. Found two very interesting and well layed out books which could be of use to others.
Programming WCF, O'Reilly Press, ISBN 10: 0-596-52699-7
Programming WPF, O'Reilly Press, ISBN 10: 0-596-51037-3

Monday, March 24, 2008

Ever get sick of converting XML to something you can post?

I've not been blogging very long but one thing that drive me mad is that Blogger won't simple let me post XML into the page. OK, I know it's because its because the rendering engine interprets the tags as HTML, but it's still annoying.

I found a website today called Postablewhich will do the conversion for you which is very handy.