Pragmatic BizTalk Services: File Sharing Service
In the previous post in this series, we have looked into BizTalk Services, opened an account and got ourselves ready for moving on to actually implementing a ISB service. Our service is a file sharing service, that roughly provides me with the following extremely important capabilities:
Let's design the data contracts first:
[DataContract]
class SharedFile
{
[DataMember]
public string FileName { get; set; }
}
[DataContract]
class SharedFileData
{
[DataMember]
public SharedFile File { get; set; }
[DataMember]
public byte[] Data { get; set; }
}
[DataContract]
class SharedDirectory
{
[DataMember]
public string DirectoryName { get; set; }
[DataMember]
public SharedFile[] Files { get; set; }
}
The service contract will be trivial at first, to provide the most basic functionality of enumerating files in a directory and retrieving a specific file:
[ServiceContract]
interface IFileSharing
{
[OperationContract]
SharedDirectory EnumerateDirectory(string directoryPath);
[OperationContract]
SharedFileData RetrieveFile(SharedFile file);
}
The service implementation is straightforward, so I'll omit it and jump straight to the interesting bits - defining the binding that will connect our service to the Internet service bus provided by BizTalk Services. In order to do that, we'll first need to reference the new System.ServiceBus.dll assembly and use the RelayBinding type which implements the intricacies of getting on the bus!
Note that the System.ServiceBus.dll assembly is not registered to appear in the .NET Add Reference dialog. You need to navigate to your BizTalk Services SDK installation path and choose the assembly from there.
The following code will get us listening on an Internet service bus address, accessible from all over the web:
ServiceHost host = new ServiceHost(typeof(FileSharingService));
host.AddServiceEndpoint(
typeof(IFileSharing),
new RelayBinding(),
"sb://connect.biztalk.net/services/sasha");
host.Open();
Note the RelayBinding and the unusual sb:// protocol scheme - other than that, there's no difference whatsoever between hosting this kind of service and any other WCF service. The extensibility features of WCF have finally paid off ;-)
By the way, if you want to transfer files larger than 64KB (which is the default message size configured on the binding), you'll need to adjust the quotas on the binding (e.g. MaxReceivedMessageSize and friends).
If you attempt to run this code (replacing "sasha" with your own account ID, of course), you will get a Windows CardSpace prompt asking you for the information card to run the service. That's fairly annoying and if we had to do that with a production service, our use of BizTalk Services wouldn't be very long. Fortunately, there are ways to configure other authorization providers (e.g. username and password, or an automatically renewing CardSpace provider) that are outside the scope of this post.
With this incredible implementation, we can move on to the client. When the client connects we enumerate the specified directory and retrieve the files, adding them to a ListBox. The code is identical with regard to the binding and address.
RelayBinding binding = new RelayBinding();
binding.SendTimeout = TimeSpan.FromMinutes(5);
IFileSharing proxy = ChannelFactory<IFileSharing>.CreateChannel(binding,
new EndpointAddress("sb://connect.biztalk.net/services/sasha"));
SharedDirectory directory = proxy.EnumerateDirectory(@"D:\Temp");
Array.ForEach(directory.Files, fd => listFiles.Items.Add(fd.FileName));
Note that I've increased the timeout on the binding - upload speeds in Israel are not quite there yet, and besides the Microsoft data center probably doesn't have the strongest servers with an awful lot of bandwidth. In other words, it's slow. But hey, it works!
Later on, when the client selects a file to be downloaded:
string file = listFiles.SelectedItem as string;
if (file == null) return;
SaveFileDialog save = new SaveFileDialog();
save.Title = "Choose save destination...";
if (save.ShowDialog() != DialogResult.OK)
return;
SharedFileData data =
proxy.RetrieveFile(new SharedFile { FileName = file });
File.WriteAllBytes(save.FileName, data.Data);
MessageBox.Show(file + " downloaded successfully to " + save.FileName);
Note that when the client starts, you'll be prompted for an information card again. I use the same card for the service and client - I don't want anyone else looking at my files. But if I wanted to grant someone else access, I could use the Identity Services web configuration page or API (in System.ServiceBus.Identity.Management) to do that. I could even grant someone access to the EnumerateDirectory operation but not the RetrieveFile operation - without changing a single line of code. (More on this in subsequent posts.)
If you want to play with the bits demonstrated in this post, you can download the complete solution. Note that you'll obviously have to replace "sasha" with your own BizTalk Services account ID.
In the next installment: using the BizTalk Services Pub/Sub capabilities for implementing a chat application across multiple users.
(BTW, if you can't constrain your curiosity regarding the ink diagrams in this and the previous post: No, I haven't bought myself a Tablet PC. I'm still using my ageing Dell XPS M1210, but I did get myself a Genius G7100 digital writing pad. I've used it to teach a recorded course at Sela a few weeks ago instead of using a whiteboard, and got addicted. I'm writing on regular paper, get the capability to transfer the written notes to the PC, and if I'm connected to a PC already - the G7100 functions as a full-fledged tablet with no special installation whatsoever. Then again, it's Vista.)