Monday, August 11, 2008

Getting to grips with Apache on the Mac - Part 1

First off, lets explain that this is only a first attempt at getting a full Web Service up and running on Leopard (OSX 10.5). Don't expect this to be an in depth explanation of the process, it is only a step by step guide and notes as I do it myself. Being a .Net developer I can find my way around IIS without any problem and moving to Unix is a bit of leap. With luck by the time I've completed this post, both you and I will know a little more than before.

1) Starting the Apache web server on 10.5 is the easy bit, as the software is already included in your installation. Simply go to the System Preferences options from the Apple icon. Select "Sharing" from the "Internet and Network" section. Click the "Web Sharing" on and you should get a HTTP link on the right showing the IP Address of the server.

2) Clicking this link will bring up a default Apache webpage showing
"If you can see this, it means that the installation of the Apache web server software on this system was successful. You may now add content to this directory and replace this page."
Clicking the site http://ipaddress/~username
brings up
"Forbidden You don't have permission to access /~username/ on this server."

3) "So where are all the files then?", I asked myself. Well, after some searching I found the configuration files in the /etc/apache2 directory on the System volume. Get there using the Terminal application and edit the "httpd.conf" file. This contains all the main information information. hostname, ServerAdmin email address, IP Address, etc.
The information in this file indicated that the root directory "INETPUB/wwwroot" in Windows is located in /Library/Webserver/Documents.

4) Editing the DocumentRoot setting in the httpd.conf file moves the home directory to a new location brings up a "readonly" error in vi. The only way around this is to make the editable is to do it with increased permissions. First off I tried to do a "su root" command, only to find that this is disabled by default on my version of OSX. However, you can use the "sudo vi httpd.conf" command to open the config file with read/write access.

5) Using XCode I create a new index.html file with some text and placed it in the required directory.

6) Start/Stop the webservice and browse to the IPAddress of your server and you won't get the correct file displayed, No, for some reason you get the default Apache not configured message. After much head scratching I discovered that it will work fine if you use Local host (i.e. 127.0.0.1) as the IP address in the browser. Why? Who knows, guess its something to do with the configuration in the .conf file. However accessing the file from another browser on the LAN worked fine!

More next time ....

Friday, August 1, 2008

Connecting BDC to SQL WebService

Recently I've been experimenting with the Business Data Connector (BDC) technology available within MOSS. Resources on the Net are generally supportive and I've found that in general it works most of the time. Connecting to Database tables is well supported and although limited, connecting to WebServices is also possible. I have however come up with a limitation that I was unable to get around, that is connecting BDC to a SQL WebService or WCF Service as opposed to a standard ASPX.

Full WCF is simply not supported, however using the basicHTTP protocol can be accomplished with a little bit of text editing of the XML. I was however surprised that the SQL WebSerice caused such an issue for the BDC Editor.

Creating the WebService within SQL is a simple matter of running the following SQL against the NorthWind database:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetProductsProc]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[GetProductsProc]
GO
CREATE PROC dbo.GetProductsProc
AS
SELECT
ProductID,
ProductName,
UnitPrice
FROM
Products
GO

DROP ENDPOINT GetProducts
GO

CREATE ENDPOINT GetProducts
STATE = STARTED
AS HTTP
(
PATH = '/Store',
AUTHENTICATION = (INTEGRATED),
PORTS = (CLEAR),
CLEAR_PORT = 8045,
SITE = '*'
)
FOR SOAP
(
WEBMETHOD 'ProductsList'
(NAME='Northwind.dbo.GetProductsProc'),
BATCHES = DISABLED,
WSDL = DEFAULT,
LOGIN_TYPE = WINDOWS,
DATABASE = 'Northwind',
NAMESPACE = 'http://Northwind/Store'
)
GO


This provides an endpoint on the URL HTTP://localhost:8045/Store?wsdl.

Opening up the BCD Editor (available in the SharePoint SDK) and connecting to the WebService by clicking Add LOB System, Connect to WebService and type in the URL to your SQL WebService.





Click Add WebMethod from the right toolbar and drag the "ProductList" method on to the canvans and click OK. Choose the standard 'WebServiceLobSystem' System Name.






At this point we would create a Finder Instance within the /Entities/Entity0/Method/ProductList/Instances node and click Execute.








However unlike a standard webservice we get the error: AdapterObject is not of RootTypeDescriptor Type. Parameter name: adapterObject.















Following a long investigation I was unable to find a resolution to this issue. It is however all related to the fact that SQL is publishing the return values as an array of System.Objects and not an array of System.String. I'm hoping that the next version of the BCD Editor will work correctly.

Full Microsoft documentation for the BDC is available from :http://msdn.microsoft.com/en-us/library/ms563661.aspx

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!