You are here:   Blog
Register   |  Login

 

Nov 19

Written by: Michael Washington
11/19/2011 7:15 PM  RssIcon

image

In this article we will create a simple chat application. If different users are using the same LightSwitch application, they will be able to chat with each other.

image

We will implement this using a LightSwitch Data Source ExtensionLightSwitchHelp​Website Cache Table Data Source.

 

Using The Cache Table Data Source

image

First, we create a new project and go into Properties.

 

image

We browse for extensions online…

 

image

Download and install the LightSwitchHelpWebsite Cache Table Data Source.

LightSwitch may need to restart to complete the installation.

 

image

You will need to go back into Properties and enable the extension.

 

image

Right-click on Data Sources and select Add Data Source

 

image

Select WCF RIA Service.

 

image

Select the CacheTable.

 

image

Check the box next to the Entity and set the seconds for the cache in the Connection String box.

Note: The setting applies to the entire table being held in cache not the individual records (messages). Also, the Data Source is using SlidingExpiration:

image

 

image

The Entity will display like a normal table.

 

image

Make sure you click on the top of the Entity (on the name), and in the Properties, set the ChatDisplay field as the Summary Property.

We do this because we will use a Summary Control, and it can only display one field. However, we want the user name and the message to display.

 

image

You can now add a Screen

 

image

For example, a Editable Grid Screen

 

image

You will be able to add, edit and delete messages as you would any other table. The only difference is that the messages will automatically be deleted based on the configured time.

 

Create The Chat Screen

image

We open the Screen.

 

image

We add a Data Item.

 

image

We add a NewMessage string property to the Screen.

 

image

We add another Data Item, a Method called Submit.

 

image

We also edit the Query…

 

image

…and set the TimeStamp to be sorted by Descending.

 

image

We lay out the screen according to the image above.

 

We use the following code for the Screen code behind:

 

namespace LightSwitchApplication
{
    public partial class EditableMessageContractsGrid
    {
        partial void Submit_Execute()
        {
            var objNewMessage = this.MessageContracts.AddNew();
            objNewMessage.Text = NewMessage;
            objNewMessage.UserName = this.Application.User.Name;
            Save();
            MessageContracts.Refresh();
            NewMessage = "";
        }
    }
}

 

image

When we run the application, we can chat.

Only TestUser can chat in debug, you must Publish the application to chat with more than one account, though you can open up another web browser and chat as TestUser using more than one web browser.

However, even if we open two web browsers, we must click the Refresh button, or enter a message, to see any new messages.

 

Auto-Refresh The Chat Screen

While it is possible to have a timer simply auto refresh the screen, this can be annoying because the screen will constantly refresh even when there are no new messages.

What we desire is this:

  • Have a “hidden” collection that is bound to all messages and refresh it every 5 seconds.
  • Compare the hidden collection to the visible collection, and only refresh the visible collection if the hidden collection has messages that the visible collection does not have.

 

image

 

First, we add MessageContractsHidden.

 

We then add the following code to the Screen code behind:

 

   partial void EditableMessageContractsGrid_InitializeDataWorkspace(List<IDataService> saveChangesTo)
    {
        // By: Timer code by Scott Hickerson
        Dispatchers.Main.BeginInvoke(() =>
        {
            Timer.Tick += new EventHandler(Each_Tick);
            Timer.Start();
        });
    }
    private static readonly DispatcherTimer Timer = new DispatcherTimer()
    {
        Interval = TimeSpan.FromSeconds(5) // Create a timer to refresh screen
    };
    private void Each_Tick(object sender, EventArgs e)
    {
        MessageContractsHidden.Refresh();
        var HighestMessageInDatabase =
            this.MessageContractsHidden
            .OrderByDescending(x => x.MessageID).FirstOrDefault();
        if (HighestMessageInDatabase != null)
        {
            Dispatchers.Main.BeginInvoke(() =>
            {
                var HighestMessage =
                    MessageContracts.OrderByDescending(x => x.MessageID).FirstOrDefault();
                int intHighestMessage = 0;
                if (HighestMessage != null)
                {
                    intHighestMessage = HighestMessage.MessageID;
                }
                if (intHighestMessage < HighestMessageInDatabase.MessageID)
                {
                    MessageContracts.Refresh();
                }
            });
        }
    }
 

 

image

When you Publish the application, you will be able to log in, and chat as different users.

Note: The cache will not expire messages until the system is idle. When users are chatting (or their screen is open and it is polling every 5 seconds), the system is not idle and the messages will not be expired. When no one is chatting the web server will then expire the cache messages.

 

Creating The LightSwitch Data Source Extension

A LightSwitch Data Source Extension is basically just a installable WCF RIA Service. You can read the official Walkthrough: Creating a Data Source Extension.

 

image

The first step is to install the LightSwitch Extensibility Toolkit. You will need to make sure you also have the prerequisites. You can get everything at this link: http://msdn.microsoft.com/en-us/library/ee256689.aspx

 

image

We create a LightSwitch Extension Library project.

 

image

We add a new item to the .lspkg project…

 

image

We add a Data Source called CacheTable.cs.

We use this code:

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Server;
using System.Web.Configuration;
namespace DataSourceExtension.DataSources
{
    public class MessageContract
    {
        [Key]
        [ReadOnly(true)]
        [Display(Name = "MessageID")]
        public int MessageID { get; set; }
        [Display(Name = "Text")]
        public string Text { get; set; }
        [Display(Name = "UserName")]
        public string UserName { get; set; }
        [ReadOnly(true)]
        [Display(Name = "TimeStamp")]
        public DateTime TimeStamp { get; set; }
        [ReadOnly(true)]
        [Display(Name = "ChatDisplay")]
        public string ChatDisplay { get; set; }
    }
    [Description("Enter the number of seconds for the cache")]
    public class CacheTable : DomainService
    {
        public static int MessageID = 0;
        public static int CacheTime = 60;
        public override void Initialize(DomainServiceContext context)
        {
            base.Initialize(context);
            //Get the CacheTime from config.
            string _CacheTime = "";
            if (WebConfigurationManager
                .ConnectionStrings[typeof(CacheTable).FullName] != null)
            {
                _CacheTime = WebConfigurationManager
                            .ConnectionStrings[typeof(CacheTable).FullName]
                            .ConnectionString;
            }
            // If nothing set or not a number - set to 60 seconds
            if (int.TryParse(_CacheTime, out CacheTime) == false)
            {
                CacheTime = 60;
            }     
        }
        #region public IQueryable<MessageContract> GetMessages()
        [Query(IsDefault = true)]
        public IQueryable<MessageContract> GetMessages()
        {
            List<MessageContract> MessagesFromCache = GetCurrentMessagesFromCache();
            return MessagesFromCache.AsQueryable();
        }
        #endregion
        #region public void InsertMessage(MessageContract objMessageContract)
        public void InsertMessage(MessageContract objMessageContract)
        {
            List<MessageContract> MessagesFromCache = GetCurrentMessagesFromCache();
            MessageID++;
            objMessageContract.MessageID = MessageID;
            objMessageContract.UserName = objMessageContract.UserName;
            objMessageContract.TimeStamp = DateTime.Now;
            objMessageContract.ChatDisplay = String.Format("{0}: {1}", 
                objMessageContract.UserName, objMessageContract.Text);
            MessagesFromCache.Add(objMessageContract);
        }
        #endregion
        #region public void UpdateMessage(MessageContract objMessageContract)
        public void UpdateMessage(MessageContract objMessageContract)
        {
            // Get all messages from cache
            List<MessageContract> MessagesFromCache = GetCurrentMessagesFromCache();
            var MessageToUpdate = (from messages in MessagesFromCache
                                   where messages.MessageID == objMessageContract.MessageID
                                   select messages).FirstOrDefault();
            if (MessageToUpdate != null)
            {
                string strText = objMessageContract.Text;
                string strUserName = objMessageContract.UserName;
                MessageContract objNewMessageContract = new MessageContract();
                objNewMessageContract.Text = strText;
                objNewMessageContract.UserName = strUserName;
                DeleteMessage(objMessageContract);
                InsertMessage(objNewMessageContract);
            }
        }
        #endregion
        #region public void DeleteMessage(MessageContract)
        public void DeleteMessage(MessageContract objMessageContract)
        {
            // Get all messages from cache
            List<MessageContract> MessagesFromCache = GetCurrentMessagesFromCache();
            var MessageToDelete = (from messages in MessagesFromCache
                                   where messages.MessageID == objMessageContract.MessageID
                                   select messages).FirstOrDefault();
            if (MessageToDelete != null)
            {
                MessagesFromCache.Remove(MessageToDelete);
            }
        }
        #endregion
        // Utility
        #region GetCurrentMessagesFromCache
        private static List<MessageContract> GetCurrentMessagesFromCache()
        {
            string key = "ChatMessages";
            List<MessageContract> colChatMessages;
            if (!CacheHelper.Get(key, out colChatMessages))
            {
                colChatMessages = new List<MessageContract>();
                CacheHelper.Add(colChatMessages, key, CacheTime);
            }
            return colChatMessages;
        }
        #endregion
        protected override int Count<T>(IQueryable<T> query)
        {
            return query.Count();
        }
    }
}

 

 

 

We use this code for CacheHelper.cs:

 

// (c) JohnnyCoder
// http://johnnycoder.com/blog/2008/12/10/c-cache-helper-class/
using System;
using System.Web;
namespace DataSourceExtension.DataSources
{
    public static class CacheHelper
    {
        /// <summary>
        /// Insert value into the cache using
        /// appropriate name/value pairs
        /// </summary>
        /// <typeparam name="T">Type of cached item</typeparam>
        /// <param name="o">Item to be cached</param>
        /// <param name="key">Name of item</param>
        public static void Add<T>(T o, string key, int ExpireSeconds)
        {
            // NOTE: Apply expiration parameters as you see fit.
            // I typically pull from configuration file.
            TimeSpan dtTimeSpan = new TimeSpan(0, 0, ExpireSeconds);
            HttpContext.Current.Cache.Insert(
                key,
                o,
                null,
                System.Web.Caching.Cache.NoAbsoluteExpiration,
                dtTimeSpan);
        }
        /// <summary>
        /// Remove item from cache
        /// </summary>
        /// <param name="key">Name of cached item</param>
        public static void Clear(string key)
        {
            HttpContext.Current.Cache.Remove(key);
        }
        /// <summary>
        /// Check for item in cache
        /// </summary>
        /// <param name="key">Name of cached item</param>
        /// <returns></returns>
        public static bool Exists(string key)
        {
            return HttpContext.Current.Cache[key] != null;
        }
        /// <summary>
        /// Retrieve cached item
        /// </summary>
        /// <typeparam name="T">Type of cached item</typeparam>
        /// <param name="key">Name of cached item</param>
        /// <param name="value">Cached value. Default(T) if
        /// item doesn't exist.</param>
        /// <returns>Cached item as type</returns>
        public static bool Get<T>(string key, out T value)
        {
            try
            {
                if (!Exists(key))
                {
                    value = default(T);
                    return false;
                }
                value = (T)HttpContext.Current.Cache[key];
            }
            catch
            {
                value = default(T);
                return false;
            }
            return true;
        }
    }
}

 

 

Download Code

The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

 

Also See:

 

6 comment(s) so far...


Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

Now.. this is cool!

Great work!

By Faris Wong on   11/22/2011 1:53 AM
Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

@Faris Wong - Thank You!

By Michael Washington on   11/22/2011 5:37 AM
Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

Hi!
I have managed to adapt your beautiful example for a table from a Sql Server 2008 Db instead of the WCF Ria Service. However, the periodic refresh doesn't occur for some reason and I'm not sure why. I think it might be because I am not allowed to define the EditableMessageContractsGrid_InitializeDataWorkspace method as partial because it wasn't declared somewhere else, but I'm not sure...
Am I missing something?

By zah.ctin on   8/24/2012 12:42 AM
Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

@zah.ctin - Please download the example code and compare it to your own.

By Michael Washington on   8/24/2012 5:06 AM
Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

Well, that's the weird thing. The part for refresh is pasted from your sample, and I only changed the table and column names to fit my own.

By zah.ctin on   8/26/2012 10:02 PM
Gravatar

Re: LightSwitch Chat Application Using A Data Source Extension

Fixed it. The issues was that it didn't recognize the ScreenName_InitializeDataWorkspace when I pasted the code. I used the Write Code for that certain event and pasted the code there and it worked.
Thank you for the example!

By zah.ctin on   8/26/2012 10:44 PM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation