Introducing SilverlightDesktop.net

Introduction

Note: You should always download the latest version of SilverlightDesktop.net from http://SilverlightDesktop.net

SilverlightDesktop.net is a Open-Source ASP.NET framework that allows you to dynamically load Silverlight modules into resizable draggable windows. It will also load modules without a window for easy integration into existing websites. SilverlightDesktop.net is available as a stand-alone program and a DotNetNuke version.

This article will explore the following topics:

What is SilverlightDesktop and why do I want to use it?

Microsoft Silverlight runs client-side, meaning, it does not run on the web server it runs in a user's web browser. You have to have a method to communicate between the web server that launches the Silverlight application, and the Silverlight application running in the user's web browser. This communication is usually performed using web services.

When you create Silverlight applications, you may discover that you need security, membership management, email, and logging. Implementing these supporting elements can require more time than creating the Silverlight application itself.

SilverlightDesktop.net provides a framework that allows you to create modules that contain only the custom functionality you need. SilverlightDesktop.net handles the security, membership and role management, as well as providing email and logging support.

Installing SilverlightDesktop.net

To install or upgrade SilverlightDesktop.net:

Further installation directions can be found at the following links:

SilverlightDesktop.net Features

When you log into the SilverlightDesktop.net administration with the username and password created during the installation, you will have access to it's features:

 

SilverlightDesktop.net Security

The security is implemented as follows:

 

The SilverlightDesktop.net launches the Silverlight control. It uses code to pass the InitParameters that contain a temporary password (it also stores the IP address).

        string strIPAddress = this.Context.Request.UserHostAddress;
        int intPassword = Authendication.SetSilverlightKey(_UserID, strIPAddress);
        string strWebServiceBase = GetWebServiceBase();

        SilverlightDesktop.InitParameters =
            String.Format("PortalID={0},ModuleId={1},UserID={2},Password={3},WebServiceBase={4},AutoLoadModule={5}",
            "-1", _DesktopID.ToString(), _UserID.ToString(), intPassword.ToString(), strWebServiceBase, AutoLoadModule);

In the App.xaml.cs file in the SilverlightDesktop project (the Silverlight application that creates the windows that the other Silverlight modules are loaded into) the code retrieves the parameters...

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // Load the main control
            this.RootVisual = new Page(e.InitParams["PortalID"], e.InitParams["ModuleId"], 
                e.InitParams["UserID"], e.InitParams["Password"], e.InitParams["WebServiceBase"], 
                e.InitParams["AutoLoadModule"]);
        }

... and passes them to the Silverlight custom module (when the custom module is dynamically loaded). It passes the parameters to the custom module through its .Tag property.

	
        private void loadAssembly(Stream s)
        {
            // from http://silverlight.net/forums/p/14941/49437.aspx (BlueAquarius)
            // From http://joel.neubeck.net/2008/03/silverlight-how-to-on-demand-assembly-deployment/

            try
            {
                Uri uri = new Uri(strXAPName.Replace(".xap", ".dll"), UriKind.Relative);
                StreamResourceInfo xapPackageSri = new StreamResourceInfo(s, null);
                StreamResourceInfo assemblySri = Application.GetResourceStream(xapPackageSri, uri);

                AssemblyPart assemblyPart = new AssemblyPart();
                Assembly a = assemblyPart.Load(assemblySri.Stream);

                UserControl objUserControl = (UserControl)a.CreateInstance(strAssemblyClassName);
                if (objUserControl == null)
                {
                    debug("objUserControl is null " + strAssemblyClassName);
                    return;
                }

                // Pass the parameters to the control that it will need to connect to the website
                objUserControl.Tag = string.Format("{0}, {1}, {2}, {3}, {4}", 
                  intPortalID, intModuleId, intUserID, strPassword, strWebServiceBase);

                if (UseWindows)
                {
                    SilverlightWindowControl objSilverlightWindowControl = 
                      new SilverlightWindowControl(objSilverlightDesktopModule.WindowSize);
                    objSilverlightWindowControl.Window.Content = objUserControl;
                    objSilverlightWindowControl.WindowName.Text = objSilverlightDesktopModule.ModuleName;
                    Canvas.SetLeft(objSilverlightWindowControl, (double)intWindowLocation);
                    Canvas.SetTop(objSilverlightWindowControl, (double)intWindowLocation);

                    this.LayoutRoot.Children.Add(objSilverlightWindowControl);
                }
                else
                {
                    this.LayoutRoot.Children.Add(objUserControl);
                }
            }
            catch (Exception ex)
            {
                debug("Exception when loading the assembly part:");
                debug(ex.ToString());
            }
        }

When a web service call is made by the custom module, the temporary password is passed to the web service.

        #region ShowWhoAmI
        private void ShowWhoAmI()
        {
            BasicHttpBinding bind = new BasicHttpBinding();
            EndpointAddress MyEndpointAddress = new EndpointAddress(strWebServiceBase + "WhoAmI.asmx");
            var proxy = new WhoAmISoapClient(bind, MyEndpointAddress);

            proxy.WhoIAmCompleted += new EventHandler(proxy_WhoIAmCompleted);
            proxy.WhoIAmAsync(intPortalID, intModuleId, intUserID, strPassword);
        }

        void proxy_WhoIAmCompleted(object sender, WhoIAmCompletedEventArgs e)
        {
            UserInfo UserInfo = (UserInfo)e.Result;
            DisplayWhoIAm(UserInfo);
        }

        private void DisplayWhoIAm(UserInfo UserInfo)
        {
            this.txtFirstName.Text = UserInfo.FirstName;
            this.txtLastName.Text = UserInfo.LastName;
            this.txtEmail.Text = UserInfo.Email;
        }
        #endregion

The web service passes this temporary password and the IP address of the Silverlight application calling it (remember the Silverlight application is running in the user's web browser) to the Authentication class that is part of he SilverlightDesktopCore project.

    #region WhoIAm
    [WebMethod(Description = "WhoIAm")]
    [ScriptMethod()]
    public UserInfo WhoIAm(int PortalID, int ModuleId, int UserID, string Password)
    {
        string strIPAddress = this.Context.Request.UserHostAddress;
        SilverlightDesktopAuthendicationHeader SilverlightDesktopAuthendicationHeader = 
          new SilverlightDesktopAuthendicationHeader();
        SilverlightDesktopAuthendicationHeader.PortalID = PortalID;
        SilverlightDesktopAuthendicationHeader.UserID = UserID;
        SilverlightDesktopAuthendicationHeader.Password = Password;
        SilverlightDesktopAuthendicationHeader.ModuleId = ModuleId;
        SilverlightDesktopAuthendicationHeader.IPAddress = strIPAddress;

        UserInfo response = new UserInfo();

        Authendication Authendication = 
         new Authendication(SilverlightDesktopAuthendicationHeader);
        if (Authendication.IsUserValid("WhoAmI"))
        {
            response = Authendication.GetUserInfo();
        }
        else
        {
            response.FirstName = "Visitor";
            response.LastName = DateTime.Now.ToLongDateString();
        }

        return response;
    }
    #endregion

The Authentication class compares the username and password to determine if the user should be authenticated (note Strings.Left(result.IPAddress,6) is used to only match the first part of an IP address in case a user is behind a proxy server that is constantly changing the IP address.

        //  Get the SilverlightKey
        var SilverlightKey = (from ASilverlightDesktopUser in SilverlightDesktopDAL.SilverlightDesktopUsers
                              where ASilverlightDesktopUser.UserID == _UserID
                              select ASilverlightDesktopUser).FirstOrDefault();

        if (SilverlightKey.SilverlightKey == ConvertToSilverlightkey(_Password) & 
            Strings.Left(SilverlightKey.IPAddress, 6) == Strings.Left(_IPAddress, 6))
        {
            return true;
        }
        else
        {
            if (Strings.Left(SilverlightUser.IPAddress,6) == Strings.Left(_IPAddress,6))
            {
                // The correct IP address was used
                // To prevent a brute force attack scramble the password
                // A hacker is now chasing a moving target
                SetSilverlightKey();
            }
            return
        }

Each time the user logs into the SilverlightDesktop.net site they are given a new temporary password.

Custom Modules

Modules can be installed by uploading a .zip file. The package manifest is described here: Creating a module package

Custom Module Development

Custom modules are created by making a Silverlight project that creates a .xap file.

The .xap is stored in the ClientBin directory of the SilverlightDesktop website. Other module elements such as web services and data access layers can also be created.

The module is configured using the Module settings in administration. More information on creating modules can be found at this link.

The Core

The SilverlightDesktop.net source consists of 5 projects:

SilverlightDesktop Project

The SilverlightDesktop project is compiled into SilverlightDesktop.xap and resides in the ClientBin directory of the SilverlightDesktop website. It is loaded when a visitor views their SilverlightDesktop or an AutoLoad module. When displaying a module in the SilverlightDesktop window mode, the project loads the class specified in the module definition, in the SilverlightWindowControl.

The SilverlightWindowControl consists of a collection of Canvas controls, Rectangle controls, and Window controls arranged to resemble a resizable window control. Event handlers are wired-up to the various elements to support the drag and resizing functionality.

The following shows the code used to manipulate the Left side:

        #region LeftSide
        private void LeftSide_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //Start Drag
            FrameworkElement LeftSide = (FrameworkElement)sender;
            LeftSide.CaptureMouse();

            // Set the starting point for the drag
            StartingDragPoint = e.GetPosition(LeftSide);
            Point ControlPoint = e.GetPosition(this);
            StartingDragPoint.X = StartingDragPoint.Y - ControlPoint.Y - .5;

            LeftSide.MouseMove += new MouseEventHandler(LeftSide_MouseMove);
            LeftSide.MouseLeftButtonUp += new MouseButtonEventHandler(LeftSide_MouseLeftButtonUp);
        }

        void LeftSide_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            //Stop Drag
            FrameworkElement LeftSide = (FrameworkElement)sender;
            LeftSide.ReleaseMouseCapture();

            LeftSide.MouseMove -= new MouseEventHandler(LeftSide_MouseMove);
            LeftSide.MouseLeftButtonUp -= new MouseButtonEventHandler(LeftSide_MouseLeftButtonUp);
        }

        void LeftSide_MouseMove(object sender, MouseEventArgs e)
        {
            Point LeftSidePoint = e.GetPosition(LeftSide);
            double PointDifference = LeftSidePoint.X - StartingDragPoint.X;

            if (PointDifference < 0)
            {
                PointDifference = PointDifference * -1;

                if (LeftSidePoint.X < (Window.Width + (PointDifference) - 60))
                {
                    this.SetValue(WidthProperty, (double)(PointDifference));
                    Window.SetValue(WidthProperty, (double)Window.Width + (PointDifference));
                    DragStackPanel.SetValue(WidthProperty, (double)DragStackPanel.Width + (PointDifference));
                    DragBar.SetValue(WidthProperty, (double)DragBar.Width + (PointDifference));
                    TopSide.SetValue(WidthProperty, (double)TopSide.Width + (PointDifference));
                    BottomSide.SetValue(WidthProperty, (double)BottomSide.Width + (PointDifference));

                    Canvas Canvas = (Canvas)this.Parent;
                    Point Point = e.GetPosition(Canvas);
                    Canvas.SetLeft(this, Point.X - StartingDragPoint.X);
                    Canvas.SetLeft(RightSide, (double)TopSide.Width + StartingDragPoint.X);
                }
            }
            else
            {
                if (LeftSidePoint.X < (Window.Width - (PointDifference) - 60))
                {
                    this.SetValue(WidthProperty, (double)(PointDifference));
                    Window.SetValue(WidthProperty, (double)Window.Width - (PointDifference));
                    DragStackPanel.SetValue(WidthProperty, (double)DragStackPanel.Width - (PointDifference));
                    DragBar.SetValue(WidthProperty, (double)DragBar.Width - (PointDifference));
                    TopSide.SetValue(WidthProperty, (double)TopSide.Width - (PointDifference));
                    BottomSide.SetValue(WidthProperty, (double)BottomSide.Width - (PointDifference));

                    Canvas Canvas = (Canvas)this.Parent;
                    Point Point = e.GetPosition(Canvas);
                    Canvas.SetLeft(this, Point.X - StartingDragPoint.X);
                    Canvas.SetLeft(RightSide, (double)TopSide.Width + StartingDragPoint.X);
                }
            }
        }
        #endregion

SilverlightDesktopCore Project

The SilverlightDesktopCore project is used by the main SilverlightDesktop website but can also be referenced by custom modules.

Some of the available classes and methods in the SilverlightDesktopCore project are shown above.

The primary functionality it provides custom modules is authentication, logging:

// Log the event
ApplicationLog.AddToLog(String.Format("Module: {0} - File deleted {1}.", 
   objModules.FirstOrDefault().ModuleName, strFileToDelete));

and emailing:

   #region Send Test Email
    protected void lnkTestEmail_Click(object sender, EventArgs e)
    {
        string[] arrAttachments = new string[0];
        string strEmailResponse = Email.SendMail(txtSMTPFrom.Text.Trim(), txtSMTPFrom.Text.Trim(), "", "", txtSMTPFrom.Text.Trim(), 
          MailPriority.Normal, "SilverlightDesktop Email", Encoding.UTF8, "A test email sent from SilverlightDesktop",
        arrAttachments, txtSMTPEmailServer.Text.Trim(), rbAuthendication.SelectedValue, txtSMTPUsername.Text.Trim(), 
          txtSMTPPassword.Text.Trim(), chkSecureAccess.Checked);

        lblUpdated.Text = (strEmailResponse.Trim() == "") ? "Email Sent." : strEmailResponse;
    }
    #endregion

SilverlightDesktop.net for DotNetNuke

SilverlightDesktop.net is also available in a version that runs inside DotNetNuke . More information can be found here:

 

http://SilverlightDesktop.net