Compartilhar via


Invoking Tier-Specific Logic from Common Code in LightSwitch

Visual Studio LightSwitch makes use of .NET portable assemblies to allow developers to write business logic that can be executed on both the client (Silverlight) and server (.NET 4) tiers.  In LightSwitch terminology, we refer to the assembly that contains this shared logic as the Common assembly.  In this post, I’m going to describe a coding pattern that allows you to invoke code from the Common assembly that has different implementations depending on which tier the code is running on.

In my scenario, I have a Product entity which has an Image property and the image must be a specific dimension (200 x 200 pixels).  I would like to write validation code for the Image property to ensure that the image is indeed 200 x 200.  But since the validation code for the Image property is contained within the Common assembly, I do not have access to the image processing APIs that allow me to determine the image dimensions.

This problem can be solved by creating two tier-specific implementations of the image processing logic and store that in classes which derive from a common base class that is defined in the Common assembly.  During the initialization of the client and server applications, an instance of the tier-specific class is created and set as a static member available from the Common assembly.  The validation code in the Common assembly can now reference that base class to invoke the logic.  I realize that may sound confusing so let’s take a look at how I would actually implement this.

This is the definition of my Product entity:

1

I now need to add some of my own class files to the LightSwitch project.  To do that, I switch the project to File View.

2

From the File View, I add a CodeBroker class to the Common project.

3

The CodeBroker class is intended to be the API to tier-specific logic.  Any code in the Common assembly that needs to execute logic which varies depending on which tier it is running in can use the CodeBroker class. Here is the implementation of CodeBroker:

C#:

 public abstract class CodeBroker
{
    private static CodeBroker current;

    public static CodeBroker Current
    {
        get { return CodeBroker.current; }
        set { CodeBroker.current = value; }
    }

    public abstract void GetPixelWidthAndHeight(byte[] image, out int width,
                                                out int height);
}

VB:

 Public MustInherit Class CodeBroker
    Private Shared m_current As CodeBroker

    Public Shared Property Current() As CodeBroker
        Get
            Return CodeBroker.m_current
        End Get
        Set(value As CodeBroker)
            CodeBroker.m_current = value
        End Set
    End Property

    Public MustOverride Sub GetPixelWidthAndHeight(image As Byte(),
                                                   ByRef width As Integer,
                                                   ByRef height As Integer)
End Class

I next add a ClientCodeBroker class to the Client project in the same way as I added the CodeBroker class to the Common project.  Here’s the implementation of ClientCodeBroker:

C#:

 using Microsoft.LightSwitch.Threading;

namespace LightSwitchApplication
{
    public class ClientCodeBroker : CodeBroker
    {
        public override void GetPixelWidthAndHeight(byte[] image, out int width,
                                                    out int height)
        {
            int bitmapWidth = 0;
            int bitmapHeight = 0;

            Dispatchers.Main.Invoke(() =>
            {
                var bitmap = new System.Windows.Media.Imaging.BitmapImage();
                bitmap.SetSource(new System.IO.MemoryStream(image));
                bitmapWidth = bitmap.PixelWidth;
                bitmapHeight = bitmap.PixelHeight;
            });

            width = bitmapWidth;
            height = bitmapHeight;
        }
    }
}

VB:

 Imports Microsoft.LightSwitch.Threading

Namespace LightSwitchApplication
    Public Class ClientCodeBroker
        Inherits CodeBroker
        Public Overrides Sub GetPixelWidthAndHeight(image As Byte(),
                                                    ByRef width As Integer,
                                                    ByRef height As Integer)
            Dim bitmapWidth As Integer = 0
            Dim bitmapHeight As Integer = 0

            Dispatchers.Main.Invoke(
                Sub()
                    Dim bitmap = New Windows.Media.Imaging.BitmapImage()
                    bitmap.SetSource(New System.IO.MemoryStream(image))
                    bitmapWidth = bitmap.PixelWidth
                    bitmapHeight = bitmap.PixelHeight
                End Sub)

            width = bitmapWidth
            height = bitmapHeight
        End Sub
    End Class
End Namespace

(By default, my application always invokes this GetPixelWidthAndHeight method from the Logic dispatcher.  So the call to invoke the logic on the Main dispatcher is necessary because BitmapImage objects can only be created on the Main dispatcher.)

To include the server-side implementation, I add a ServerCodeBroker class to the Server project.  It’s also necessary to add the following assembly references in the Server project because of dependencies in my image code implementation: PresentationCore, WindowsBase, and System.Xaml.  Here is the implementation of ServerCodeBroker:

C#:

 public class ServerCodeBroker : CodeBroker
{
    public override void GetPixelWidthAndHeight(byte[] image, out int width,
                                                out int height)
    {
        var bitmap = new System.Windows.Media.Imaging.BitmapImage();
        bitmap.BeginInit();
        bitmap.StreamSource = new System.IO.MemoryStream(image);
        bitmap.EndInit();
        width = bitmap.PixelWidth;
        height = bitmap.PixelHeight;
    }
}

VB:

 Public Class ServerCodeBroker
    Inherits CodeBroker
    Public Overrides Sub GetPixelWidthAndHeight(image As Byte(),
                                                ByRef width As Integer,
                                                ByRef height As Integer)
        Dim bitmap = New System.Windows.Media.Imaging.BitmapImage()
        bitmap.BeginInit()
        bitmap.StreamSource = New System.IO.MemoryStream(image)
        bitmap.EndInit()
        width = bitmap.PixelWidth
        height = bitmap.PixelHeight
    End Sub
End Class

The next thing is to write the code that instantiates these broker classes.  This is done in the Application_Initialize method for both the client and server Application classes.  For the client Application code, I switch my project back to Logical View and choose “View Application Code (Client)” from the right-click context menu of the project.

4

In the generated code file, I then add the following initialization code:

C#:

 public partial class Application
{
    partial void Application_Initialize()
    {
        CodeBroker.Current = new ClientCodeBroker();
    }
}

VB:

 Public Class Application
    Private Sub Application_Initialize()
        CodeBroker.Current = New ClientCodeBroker()
    End Sub
End Class

This initializes the CodeBroker instance for the client tier when the client application starts.

I need to do the same thing for the server tier.  There is no context menu item available for editing the server application code but the code file can be added manually.  To do this, I switch my project back to File View and add an Application class to the Server project.

5

The implementation of this class is very similar to the client application class.  Since the server’s Application_Initialize method is invoked for each client request, I need to check whether the CodeBroker.Current property has already been set from a previous invocation.  Since the CodeBroker.Current property is static, its state remains in memory across multiple client requests.

C#:

 public partial class Application
{
    partial void Application_Initialize()
    {
        if (CodeBroker.Current == null)
        {
            CodeBroker.Current = new ServerCodeBroker();
        }
    }
}

VB:

 Public Class Application
    Private Sub Application_Initialize()
        If CodeBroker.Current Is Nothing Then
            CodeBroker.Current = New ServerCodeBroker()
        End If
    End Sub
End Class

The next step is to finally add my Image property validation code.  To do this, I switch my project back to Logical View, open my Product entity, select my Image property in the designer, and choose “Image_Validate” from the Write Code drop-down button.

6

In the generated code file, I add this validation code:

C#:

 public partial class Product
{
    partial void Image_Validate(EntityValidationResultsBuilder results)
    {
        if (this.Image == null)
        {
            return;
        }

        int width;
        int height;
        CodeBroker.Current.GetPixelWidthAndHeight(this.Image, out width,
                                                  out height);

        if (width != 200 && height != 200)
        {
            results.AddPropertyError(
                "Image dimensions must be 200x200.",
                this.Details.Properties.Image);
        }
    }
}

VB:

 Public Class Product
    Private Sub Image_Validate(results As EntityValidationResultsBuilder)
        If Me.Image Is Nothing Then
            Return
        End If


        Dim width As Integer
        Dim height As Integer
        CodeBroker.Current.GetPixelWidthAndHeight(Me.Image, width, height)

        If width <> 200 AndAlso height <> 200 Then
            results.AddPropertyError("Image dimensions must be 200x200.",
                                     Me.Details.Properties.Image)
        End If
    End Sub
End Class

This code can execute on both the client and the server.  When running on the client, CodeBroker.Current will return the ClientCodeBroker instance and provide the client-specific implementation for this logic.  And when running on the server, CodeBroker.Current will return the ServerCodeBroker instance and provide the server-specific implementation.

And there you have it.  This pattern allows you to write code that is invoked from the Common assembly but needs to vary depending on which tier is executing the logic.  I hope this helps you out in your LightSwitch development.