Nov
19
Written by:
Michael Washington
11/19/2011 7:15 PM
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.
We will implement this using a LightSwitch Data Source Extension, LightSwitchHelpWebsite Cache Table Data Source.
Using The Cache Table Data Source
First, we create a new project and go into Properties.
We browse for extensions online…
Download and install the LightSwitchHelpWebsite Cache Table Data Source.
LightSwitch may need to restart to complete the installation.
You will need to go back into Properties and enable the extension.
Right-click on Data Sources and select Add Data Source
Select WCF RIA Service.
Select the CacheTable.
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:
The Entity will display like a normal table.
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.
You can now add a Screen…
For example, a Editable Grid Screen…
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
We open the Screen.
We add a Data Item.
We add a NewMessage string property to the Screen.
We add another Data Item, a Method called Submit.
We also edit the Query…
…and set the TimeStamp to be sorted by Descending.
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 = "";
}
}
}
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.
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();
}
});
}
}
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.
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
We create a LightSwitch Extension Library project.
We add a new item to the .lspkg project…
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...
Now.. this is cool!
Great work!
By Faris Wong on
11/22/2011 1:53 AM
|
@Faris Wong - Thank You!
By Michael Washington on
11/22/2011 5:37 AM
|
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
|
@zah.ctin - Please download the example code and compare it to your own.
By Michael Washington on
8/24/2012 5:06 AM
|
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
|
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
|