- Posted by Nick Van den Abbeele on September 30, 2009
Last week, I did a presentation on Windows Azure at HOWEST, University College West Flanders. As promised, my hands-on-labs of this session.
1. A file archive application to upload/download files into a database in the cloud. This application consists out of a webpage (UploadFile.aspx) to upload files into a SQL Azure database, and a REST service to dowload your files as a stream. You can dowload the sources at : CodeArchiveService.zip
To get you up and running in the develoment fabric, first create a local database in SQL Server and add a table called CodeArchive. Modify the config files to point to your database.
CREATE TABLE [dbo].[ZipArchive](
[CodeName] [varchar](200) PRIMARY KEY CLUSTERED NOT NULL,
[CodeBlob] [varbinary](max) NULL)
Next, to deploy into the cloud: create a database in SQL Azure, create a login and grant access to your database and create a table called CodeArchive (same as above). Now, change the config files to point to this database.
-- On the master database:
CREATE LOGIN <login> WITH password='<password>'
-- On your database:
CREATE USER <user> FROM LOGIN <login>
EXEC sp_addrolemember 'db_owner', '<user>'
2. A photo browser application :
-
Contains a synchronous Upload.aspx (storage right into SQL Azure) and an asynchronous UploadAsync.aspx (blob storage to store the image temporarily + queued messaging to send a message to a background workerrole)
-
Contains a workerrole for thumbnailing the image and saving both the image and the thumbnail in SQL Azure
-
Contains a Silverlight photo browser app which talks to a WCF service to retrieve it's data (no rocket science here to be focused on Azure stuff as much as possible)
You can dowload the sources at : PhotoBookService.zip
Tables needed (both locally and in SQL Azure):
CREATE TABLE [Photo](
[PhotoID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
[ThumbNailPhoto] [varbinary](max) NULL,
[LargePhoto] [varbinary](max) NULL)
CREATE TABLE [Comment](
[PhotoID] [int] NOT NULL,
[CommentID] [int] IDENTITY(1,1) NOT NULL,
[Comments] [varchar](1000) NULL,
CONSTRAINT [PK_Comment_PhotoID_CommentID] PRIMARY KEY CLUSTERED
(
[PhotoID] ASC,
[CommentID] ASC
))
ALTER TABLE [dbo].[Comment] WITH CHECK ADD CONSTRAINT [FK_Photo_Comment] FOREIGN KEY([PhotoID])
REFERENCES [dbo].[Photo] ([PhotoID])
ALTER TABLE [dbo].[Comment] CHECK CONSTRAINT [FK_Photo_Comment]
- Posted by Nick Van den Abbeele on September 15, 2009
My previous post IErrorHandler: A generic Fault Converter (1/2) describes how to build an ErrorHandler for WCF. The challenge is now to write a generic ErrorHander which can be configured to automatically convert exceptions into faults based upon a pre-defined list of converter methods. Converting an exception into a fault is one thing, but we need to be careful that we only return those faults that the operation contract defines (by means of FaultContract attributes)
Lets first focus on the implementation of the converter : we need a delegate definition for a method that takes any Exception as input and that converts this into a Fault. Faults are just classes that have a DataContract attribute (or even without when trusting on POCO support introduced in .NET 3.5 SP1). Since there isn’t any base class for FaultContracts, let’s define the type as generic.
public delegate TFault FaultConverter<TFault>(Exception e);
The converter maintains a Dictionary of exception types as keys and FaultConverter<TFault> delegates as values. We need to be able to add new converter methods and execute the converter method that suits the exception type.
public class ExceptionToFaultConverter
{
private Dictionary<Type, Delegate> lst = new Dictionary<Type, Delegate>();
public void AddConverter<TException, TFault>(FaultConverter<TFault> converter)
{
lst.Add(typeof(TException), converter);
}
public object Convert(Exception ex)
{
if (lst.ContainsKey(ex.GetType()))
{
return lst[ex.GetType()].DynamicInvoke(ex);
}
return null;
}
}
Adding a converter just looks like this :
faultConverter.AddConverter<DivideByZeroException, Faulty>(e => new Faulty() { ErrorMessage = e.Message });
During ProvideFault(), theFautlConverter will convert the Exception info a fault (if any). Next thing to to is checking if we can return this fault for the current operation.
public class MyErrorHandler : Attribute, IErrorHandler, IServiceBehavior
{
ExceptionToFaultConverter faultConverter =new ExceptionToFaultConverter();
public MyErrorHandler()
{
faultConverter.AddConverter<DivideByZeroException, Faulty>(
e => new Faulty() { ErrorMessage = e.Message }
);
}
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error,
System.ServiceModel.Channels.MessageVersion version,
ref System.ServiceModel.Channels.Message fault)
{
ServiceDescription description = OperationContext.Current.Host.Description;
ServiceEndpoint endpoint = description.Endpoints.Find(OperationContext.Current.IncomingMessageHeaders.To);
string operationName =OperationContext.Current.IncomingMessageHeaders.Action.Replace(
endpoint.Contract.Namespace + endpoint.Contract.Name + "/", "");
OperationDescription operation =endpoint.Contract.Operations.Find(operationName);
object faulttype = faultConverter.Convert(error);
if (faulttype == null)
return;
foreach (FaultDescription faultdescription in operation.Faults)
{
if (faultdescription.DetailType.IsAssignableFrom(faulttype.GetType()))
{
fault = Message.CreateMessage(
version,
FaultCode.CreateSenderFaultCode(
faultdescription.Name,
faultdescription.Namespace),
faulttype.ToString(),
faulttype,
faultdescription.Action);
break;
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher disp in serviceHostBase.ChannelDispatchers)
{
disp.ErrorHandlers.Add(this);
}
}
public void Validate(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
}