views:

51

answers:

3

Hello,

I'm implementing a messaging system in my program. It functions just like e-mail except that it is entirely contained within the system.

I was wondering if anyone has seen an e-mail control template that I could modify. It would have stuff like an inbox, message viewing and message sending parts.

I would just rewire the mail sending & receiving parts to my hook up to my tables.

Thanks!

Edit:

Ok I didn't realize I wrote so much. Make sure you credit Biff_MaGriff if you use this. :D Tech is .net 3.5 with SubSonic 3.0

The rest is in my answer.

Should look like this in the end. (Minus all my cut outs)

alt text

web.config

<pages>
    <controls>
        ...
        <add tagPrefix="cc" tagName="MessageFolder" src="~/UserControls/Messages/MessageFolder/MessageFolder.ascx"/>
        <add tagPrefix="cc" tagName="MessageView" src="~/UserControls/Messages/MessageView/MessageView.ascx"/>
        <add tagPrefix="cc" tagName="MessageCompose" src="~/UserControls/Messages/MessageCompose/MessageCompose.ascx"/>
        <add tagPrefix="cc" tagName="MessageBox" src="~/UserControls/Messages/MessageBox/MessageBox.ascx"/>
        ...
    </controls>
<pages>

App_Code/IMessageFolder.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

/// <summary>
/// Summary description for IMessageFolder
/// </summary>
public interface IMessageFolder
{
    int? MessageID { get; }
}

App_Code/CustomDataViews.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Linq;
using System.Web;
/// <summary>
/// Summary description for CustomDataViews
/// </summary>
public class CustomDataViews
{
    //Subsonic db
    static VAC.Data.VACDB db = new VAC.Data.VACDB();

    public class MessageQuery
    {
        public class Message
        {
            public int MessageID { get; set; }
            public string FromUser { get; set; }
            public string ToUser { get; set; }
            public DateTime SentDate { get; set; }
            public string Subject { get; set; }
            public string Body { get; set; }
            public bool IsDeletedSender { get; set; }
            public bool IsDeletedRecipient { get; set; }
            public bool IsRead { get; set; }
        }

        public enum MailType { ReceivedMail, SentMail };

        public Message[] GetMessages(MailType mt, bool showDeleted)
        {
            List<Message> myMessages = new List<Message>();

            foreach (var v in (from m in db.Messages
                               where (mt == MailType.ReceivedMail ? m.ToUserID : m.FromUserID) == Common.CurrentUserID
                               && (showDeleted || !(mt == MailType.ReceivedMail ? m.IsDeletedRecipient : m.IsDeletedSender))
                               orderby m.SentDate descending
                               select m))
            {
                Message mess = new Message();
                mess.MessageID = v.MessageID;
                mess.FromUser = (from u in db.Users where u.UserID == v.FromUserID select u.UserName).First();
                mess.ToUser = (from u in db.Users where u.UserID == v.ToUserID select u.UserName).First();
                mess.SentDate = v.SentDate;
                mess.Subject = v.Subject;
                mess.Body = v.Body;
                mess.IsDeletedSender = v.IsDeletedSender;
                mess.IsDeletedRecipient = v.IsDeletedRecipient;
                mess.IsRead = v.IsRead;
                myMessages.Add(mess);
            }

            return myMessages.ToArray();
        }

        public Message GetMessage(int MessageID)
        {
            var myMessage = (from m in db.Messages where m.MessageID == MessageID select m);
            if (myMessage.Count() == 1)
            {
                var v = myMessage.First();
                Message mess = new Message();
                mess.MessageID = v.MessageID;
                mess.FromUser = (from u in db.Users where u.UserID == v.FromUserID select u.UserName).First();
                mess.ToUser = (from u in db.Users where u.UserID == v.ToUserID select u.UserName).First();
                mess.SentDate = v.SentDate;
                mess.Subject = v.Subject;
                mess.Body = v.Body;
                return mess;
            }
            else
                return null;
        }
    }

}

App_Code/Common.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.SessionState;
using System.Web.Configuration;
using System.Data;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Linq;

/// <summary>
/// Summary description for Common
/// </summary>
public class Common
{
    /// <summary>
    /// Retrieves the index of the column from the gridview. Returns -1 if not found.
    /// </summary>
    /// <param name="gv"></param>
    /// <param name="columnName"></param>
    /// <returns></returns>
    static public int GetColumnIndex(GridView gv, string columnName)
    {
        int returnMe = -1;
        for (int i = 0; i < gv.Columns.Count; i++)
        {
            if (gv.Columns[i].HeaderText == columnName)
            {
                returnMe = i;
                break;
            }
        }
        return returnMe;
    }

    //Colours for gridviews
    static public string BGColourRed = "#FF8888";
    static public string BGColourRedAlternate = "#FF0000";

    static public string BGColourBlue = "#AAAAFF";
    static public string BGColourBlueAlternate = "#4444FF";

    static public string BGColourGreen = "#2bf053";
    static public string BGColourGreenAlternate = "#27c319";

    static public string BGColourOrange = "#FFA365";
    static public string BGColourOrangeAlternate = "#FF6600";

    static public string NormalBGColour(object sender)
    {
        return "#" +
            ((GridView)sender).RowStyle.BackColor.R.ToString("X") +
            ((GridView)sender).RowStyle.BackColor.G.ToString("X") +
            ((GridView)sender).RowStyle.BackColor.B.ToString("X");
    }

    static public string AlternateBGColour(object sender)
    {
        return "#" +
            ((GridView)sender).AlternatingRowStyle.BackColor.R.ToString("X") +
            ((GridView)sender).AlternatingRowStyle.BackColor.G.ToString("X") +
            ((GridView)sender).AlternatingRowStyle.BackColor.B.ToString("X");
    }

    static public string SelectedBGColour(object sender)
    {
        string selectedBGColour = "#165EA9";

        if (!((GridView)sender).SelectedRowStyle.BackColor.IsEmpty)
        {
            selectedBGColour = "#" +
               ((GridView)sender).SelectedRowStyle.BackColor.R.ToString("X") +
               ((GridView)sender).SelectedRowStyle.BackColor.G.ToString("X") +
               ((GridView)sender).SelectedRowStyle.BackColor.B.ToString("X");
        }
        return selectedBGColour;
    }

    /// <summary>
    /// Gridview RowDataBound extension.
    /// Allows row selection by clicking on a row and highlights the row in yellow on the mouse hover.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static public void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.DataItemIndex == -1)
            return;

        //e.Row.Attributes.Add("onclick", ((GridView)sender).Page.ClientScript.GetPostBackEventReference((GridView)sender, "Select$" + e.Row.RowIndex.ToString(), false));

        string selectedBGColour = SelectedBGColour(sender);
        string normalBGColour = NormalBGColour(sender);
        string alternateBGColour = AlternateBGColour(sender);

        ApplyStylingToRow(e.Row, selectedBGColour, normalBGColour, alternateBGColour);
    }

    static public void ApplyStylingToRow(GridViewRow row, string selectedBGColour, string normalBGColour, string alternateBGColour)
    {
        AppendAttribute(row, "onMouseOver",
               "this.style.cursor='pointer'; this.style.background='yellow';");
        AppendAttribute(row, "onMouseOut",
            "this.style.background='" + ((row.RowState & DataControlRowState.Selected) == DataControlRowState.Selected ? selectedBGColour :
            (row.RowState & DataControlRowState.Alternate) == DataControlRowState.Alternate ? alternateBGColour : normalBGColour) + "';");
    }


    static public void GridViewAddRowClick(GridView gv, GridViewRow row, bool isRender)
    {
        AppendAttribute(row, "onclick", gv.Page.ClientScript.GetPostBackEventReference(gv, "Select$" + row.RowIndex.ToString(), isRender) + ";");
        /*
        for (int i = 0; i < gv.Columns.Count; i++)
            if (!string.IsNullOrEmpty(gv.Columns[i].HeaderText) && row.Cells.Count > 1)
                AppendAttribute(row.Cells[i], "onclick", gv.Page.ClientScript.GetPostBackEventReference(gv, "Select$" + row.RowIndex.ToString(), isRender) + ";");
         * */
    }

    static public void GridViewRowClick_Render(GridView gv)
    {
        foreach (GridViewRow row in gv.Rows)
            if (row.RowType == DataControlRowType.DataRow)
                GridViewAddRowClick(gv, row, true);
        gv.Page.ClientScript.RegisterForEventValidation(gv.UniqueID);
    }

        /// <summary>
    /// Hides a column on a gridview.
    /// </summary>
    /// <param name="gv"></param>
    /// <param name="columnIndex"></param>
    static public void HideColumn(GridView gv, int columnIndex)
    {
        if (gv.HeaderRow != null)
            gv.HeaderRow.Cells[columnIndex].Style.Add("display", "none");
        foreach (GridViewRow row in gv.Rows)
        {
            if (row.RowType == DataControlRowType.DataRow)
                row.Cells[columnIndex].Style.Add("display", "none");
        }
    }

    /// <summary>
    /// Registers javascript to be run.
    /// </summary>
    /// <param name="con"></param>
    /// <param name="script"></param>
    static public void RegisterStartupScript(object con, string script)
    {
        RegisterStartupScript((Control)con, script);
    }

    /// <summary>
    /// Capitalizes the beginning of strings
    /// </summary>
    /// <param name="strText"></param>
    public static string InitCap(string strText)
    {
        return new CultureInfo("en").TextInfo.ToTitleCase(strText.ToLower());
    }

    /// <summary>
    /// Registers javascript to be run.
    /// </summary>
    /// <param name="con"></param>
    /// <param name="script"></param>
    static public void RegisterStartupScript(Control con, string script)
    {
        ScriptManager sm = ScriptManager.GetCurrent(con.Page);
        if (sm != null)
            ScriptManager.RegisterStartupScript(con, con.GetType(), Guid.NewGuid().ToString(), script, true);
        else
            con.Page.ClientScript.RegisterStartupScript(typeof(Page), Guid.NewGuid().ToString(), script, true);
    }

    /// <summary>
    /// Registers a javascript file to be included in the page.
    /// </summary>
    /// <param name="con"></param>
    /// <param name="url"></param>
    static public void RegisterClientScriptInclude(Control con, string url)
    {
        ScriptManager sm = ScriptManager.GetCurrent(con.Page);
        if (sm != null)
            ScriptManager.RegisterClientScriptInclude(con, con.GetType(), Guid.NewGuid().ToString(), System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath + url);
        else
            con.Page.ClientScript.RegisterClientScriptInclude(typeof(Page), Guid.NewGuid().ToString(), System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath + url);
    }

    public static int CurrentUserID { get { return (from u in new VAC.Data.VACDB().Users where u.UserName == HttpContext.Current.User.Identity.Name select u.UserID).First(); } }

    public class MessageComposeEventArgs : EventArgs
    {
        public int? SendToUserID { get; set; }
        public string Subject { get; set; }

        public MessageComposeEventArgs()
        {
        }

        public MessageComposeEventArgs(int SendToUserID)
        {
            this.SendToUserID = SendToUserID;
        }

        public MessageComposeEventArgs(int SendToUserID, string Subject)
        {
            this.SendToUserID = SendToUserID;
            this.Subject = Subject;
        }
    }
}

App_Code/ExtendedCommandField.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;


/// <summary>
/// An extended <see cref="CommandField"/> that allows deletions
/// to be confirmed by the user.
/// </summary>
public class ExtendedCommandField : CommandField
{
    /// <summary>
    /// Initialize the cell.
    /// </summary>
    public override void InitializeCell(DataControlFieldCell cell,
        DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
    {
        base.InitializeCell(cell, cellType, rowState, rowIndex);
        if (!string.IsNullOrEmpty(this.DeleteConfirmationText) && this.ShowDeleteButton)
        {
            foreach (Control control in cell.Controls)
            {
                IButtonControl button = control as IButtonControl;
                if (button != null && button.CommandName == "Delete")
                    // Add delete confirmation
                    ((WebControl)control).Attributes.Add("onclick", string.Format
                    ("if (!confirm('{0}')) return false;", this.DeleteConfirmationText));
            }
        }
    }

    #region DeleteConfirmationText
    /// <summary>
    /// Delete confirmation text.
    /// </summary>
    [Category("Behavior")]
    [Description("The text shown to the user to confirm the deletion.")]
    public string DeleteConfirmationText
    {
        get { return this.ViewState["DeleteConfirmationText"] as string; }
        set { this.ViewState["DeleteConfirmationText"] = value; }
    }
    #endregion
}

~/UserControls/Messages/MessageFolder/MessageFolder.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MessageFolder.ascx.cs"
    Inherits="UserControls_Messages_MessageFolder_MessageFolder" %>
<asp:UpdatePanel ID="up1" runat="server">
    <ContentTemplate>
        <asp:HiddenField ID="hfMessageID" runat="server" />
        <asp:Button ID="butRefresh" runat="server" Text="Check for new Messages" /><asp:Button
            ID="butCompose" runat="server" Text="Compose New Message" OnClick="butCompose_Click" /><asp:CheckBox
                AutoPostBack="true" ID="cbShowDeleted" runat="server" Text="Show Deleted Messages"
                OnCheckedChanged="cbShowDeleted_CheckChanged" />
        <asp:GridView ID="gvMessages" runat="server" DataKeyNames="MessageID" AutoGenerateColumns="false"
            OnRowDataBound="gvMessages_RowDataBound" OnSelectedIndexChanged="gvMessages_SelectedIndexChanged"
            OnRowDeleting="gvMessages_RowDeleting" Width="100%" Style="float: left;">
            <Columns>
                <asp:BoundField DataField="MessageID" InsertVisible="false" />
                <cc:ExtendedCommandField DeleteConfirmationText="Are you certain you wish to delete this record?"
                    DeleteText="Delete" ShowDeleteButton="true" />
                <asp:BoundField DataField="FromUser" HeaderText="From" />
                <asp:BoundField DataField="ToUser" HeaderText="To" />
                <asp:TemplateField>
                    <HeaderTemplate>
                        Sent Date</HeaderTemplate>
                    <ItemTemplate>
                        <asp:Label ID="lbl1" runat="server" Text='<%# Bind("SentDate") %>' Style="white-space: nowrap;" /></ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField DataField="Subject" HeaderText="Subject" ItemStyle-Width="100%" />
            </Columns>
        </asp:GridView>
    </ContentTemplate>
</asp:UpdatePanel>
A: 

If you're willing to fork out some money, Telerik has a nice PanelBar control that looks just like Outlook. I believe the samples that ship with it are identical to the Outlook-like demo application on their site, so that would probably be a very quick and easy way to get started.

Steve Danner
A: 

By Steve Danner:

Telerik has a nice PanelBar control that looks just like Outlook. I believe the samples that ship with it are identical to the Outlook-like demo application on their site, so that would probably be a very quick and easy way to get started.

FYI, The panelbar control is just a UI, it doesn't provide the messaging functionality. Of course, you can use it to make your messaging system look like outlook, but you still have to do the coding on your own.

Gan
+1  A: 

I ended up creating my own user control. If anyone is interested message and I'll post the details.

Edit:

Ok I didn't realize I wrote so much. Make sure you credit Biff_MaGriff if you use this. :D Tech is .net 3.5 with SubSonic 3.0

Should look like this in the end.

Hmm, can't post over 30k characters...

Continued from above.

~/UserControls/Messages/MessageFolder/MessageFolder.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class UserControls_Messages_MessageFolder_MessageFolder : System.Web.UI.UserControl, IMessageFolder
{
    private static int IDCOLUMNINDEX = 0;
    private static int DELETECOLUMNINDEX = 1;
    private static int FROMCOLUMNINDEX = 2;
    private static int TOCOLUMNINDEX = 3;

    public event SelectedIndexChangedEventHandler SelectedIndexChanged;
    public delegate void SelectedIndexChangedEventHandler(object sender, EventArgs e);

    public event ComposeMessageEventHandler ComposeMessage;
    public delegate void ComposeMessageEventHandler(object sender, Common.MessageComposeEventArgs e);

    VAC.Data.VACDB db = new VAC.Data.VACDB();

    public int? MessageID
    {
        get
        {
            int id;
            if (int.TryParse(hfMessageID.Value, out id))
                return id;
            else
                return null;
        }
    }

    public CustomDataViews.MessageQuery.MailType MailType { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (MailType == CustomDataViews.MessageQuery.MailType.SentMail)
        {
            butCompose.Visible = false;
        }
        Refresh();
    }

    public void Refresh()
    {
        gvMessages.DataSource = new CustomDataViews.MessageQuery().GetMessages(MailType, cbShowDeleted.Checked);
        gvMessages.DataBind();
        Common.HideColumn(gvMessages, IDCOLUMNINDEX);
        if (MailType == CustomDataViews.MessageQuery.MailType.ReceivedMail)
            Common.HideColumn(gvMessages, TOCOLUMNINDEX);
        else
            Common.HideColumn(gvMessages, FROMCOLUMNINDEX);
    }

    protected void gvMessages_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        Common.GridView_RowDataBound(sender, e);
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            CustomDataViews.MessageQuery.Message dataItem = ((CustomDataViews.MessageQuery.Message)e.Row.DataItem);

            if (dataItem.IsDeletedSender && MailType == CustomDataViews.MessageQuery.MailType.SentMail
                || dataItem.IsDeletedRecipient && MailType == CustomDataViews.MessageQuery.MailType.ReceivedMail)
            {
                foreach (TableCell tc in e.Row.Cells)
                    tc.Enabled = false;
                e.Row.Cells[DELETECOLUMNINDEX].Enabled = true;
                ((LinkButton)e.Row.Cells[DELETECOLUMNINDEX].Controls[0]).Text = "Undelete";
                ((LinkButton)e.Row.Cells[DELETECOLUMNINDEX].Controls[0]).Attributes.Clear();
            }
            if (!dataItem.IsRead && MailType == CustomDataViews.MessageQuery.MailType.ReceivedMail)
            {
                //colouring for initial load
                if ((e.Row.RowState & DataControlRowState.Alternate) == DataControlRowState.Alternate)
                    e.Row.BackColor = System.Drawing.ColorTranslator.FromHtml(Common.BGColourGreenAlternate);
                else
                    e.Row.BackColor = System.Drawing.ColorTranslator.FromHtml(Common.BGColourGreen);
                //colouring for onmouse out
                Common.ApplyStylingToRow(e.Row, Common.SelectedBGColour(sender), Common.BGColourGreen, Common.BGColourGreenAlternate);
            }
        }

        if (ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack && e.Row.RowType == DataControlRowType.DataRow)
            Common.GridViewAddRowClick(gvMessages, e.Row, false);
    }

    protected void gvMessages_SelectedIndexChanged(object sender, EventArgs e)
    {
        hfMessageID.Value = gvMessages.SelectedValue != null ? gvMessages.SelectedValue.ToString() : "";
        if (MailType == CustomDataViews.MessageQuery.MailType.ReceivedMail)
        {
            var mess = (from m in db.Messages where m.MessageID == MessageID.Value select m);
            if (mess.Count() == 1)
            {
                var message = mess.First();
                message.IsRead = true;
                message.Save();
            }
        }

        if (this.SelectedIndexChanged != null)
            SelectedIndexChanged(this, new EventArgs());
        this.Refresh();
    }

    protected void gvMessages_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
        int id;
        if (int.TryParse(((GridView)sender).Rows[e.RowIndex].Cells[IDCOLUMNINDEX].Text, out id))
        {
            var message = (from m in db.Messages where m.MessageID == id select m).First();
            if (!message.IsDeletedRecipient && MailType == CustomDataViews.MessageQuery.MailType.ReceivedMail)
                message.IsDeletedRecipient = true;
            else
                message.IsDeletedRecipient = false;
            message.Save();

            if (!message.IsDeletedSender && MailType == CustomDataViews.MessageQuery.MailType.SentMail)
                message.IsDeletedSender = true;
            else
                message.IsDeletedSender = false;
            message.Save();

            gvMessages.SelectedIndex = -1;
            gvMessages_SelectedIndexChanged(gvMessages, new EventArgs());
        }
        Refresh();
    }

    protected override void Render(HtmlTextWriter writer)
    {
        Common.GridViewRowClick_Render(gvMessages);
        base.Render(writer);
    }

    protected void butCompose_Click(object sender, EventArgs e)
    {
        if (this.ComposeMessage != null)
        {
            this.ComposeMessage(this, new Common.MessageComposeEventArgs());
        }
    }

    protected void cbShowDeleted_CheckChanged(object sender, EventArgs e)
    {
        Refresh();
    }
}

MessageCompose.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MessageCompose.ascx.cs"
    Inherits="UserControls_Messages_MessageCompose_MessageCompose" %>
<div>
    <table style="width: 100%;">
        <tr>
            <td>
                <asp:Label ID="Label1" runat="server" Text="To :" />
            </td>
            <td style="width:100%">
                <asp:DropDownList ID="ddlToUser" runat="server" AppendDataBoundItems="true" DataSourceID="odsUsers"
                    DataTextField="UserName" DataValueField="UserID">
                    <asp:ListItem Text="--Select--" Value="" />
                </asp:DropDownList>
                <asp:ObjectDataSource ID="odsUsers" runat="server" SelectMethod="GetUsers" TypeName="CustomDataViews+UserListQuery">
                </asp:ObjectDataSource>
                <asp:RequiredFieldValidator ID="rfv1" runat="server" Text="*" ControlToValidate="ddlToUser"
                    ValidationGroup="Compose" />
            </td>
        </tr>
        <tr>
            <td>
                <asp:Label ID="Label2" runat="server" Text="Subject :" />
            </td>
            <td style="padding: 1px 5px 1px 1px">
                <asp:TextBox ID="tbSubject" runat="server" Width="100%" />
            </td>
        </tr>
        <tr>
            <td colspan="2" style="padding: 1px 5px 1px 1px">
                <asp:TextBox ID="tbBody" runat="server" TextMode="MultiLine" Style="width: 100%"
                    Rows="20" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <table width="100%">
                    <tr>
                        <td align="center">
                            <asp:Label ID="lblMessageSentText" runat="server" Text="Your message was sent." CssClass="info" Visible="false" />
                        </td>
                        <td align="right">
                            <asp:Button ID="butSend" runat="server" Text="Send" OnClick="butSend_Click" ValidationGroup="Compose"
                                 />
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</div>

MessageCompose.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using VAC.Data;

public partial class UserControls_Messages_MessageCompose_MessageCompose : System.Web.UI.UserControl
{
    public event MessageSentEventHandler MessageSent;
    public delegate void MessageSentEventHandler(object sender, EventArgs e);

    public int? SendToUserID
    {
        set
        {
            if (ddlToUser.Items.FindByValue(value.HasValue ? value.Value.ToString() : "") != null)
                ddlToUser.SelectedValue = value.HasValue ? value.Value.ToString() : "";
            else
                Common.RegisterStartupScript(this, "alert('Error :  You are unauthorized to respond to this message.');");
        }
    }

    public string SendToSubject
    {
        set
        {
            tbSubject.Text = "RE: " + value;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        lblMessageSentText.Visible = false;
    }

    private static string SENTMESSAGETEXT = "Your message was sent at {0} to {1}.";

    protected void butSend_Click(object sender, EventArgs e)
    {
        Message m = new Message();
        m.Body = tbBody.Text;
        m.FromUserID = Common.CurrentUserID;
        m.IsRead = false;
        m.SentDate = DateTime.Now;
        m.Subject = tbSubject.Text;
        int tid;
        if (int.TryParse(ddlToUser.SelectedValue, out tid))
            m.ToUserID = tid;
        m.Save();
        lblMessageSentText.Text = string.Format(SENTMESSAGETEXT, DateTime.Now.ToString(), ddlToUser.SelectedItem.Text);
        lblMessageSentText.Visible = true;

        ddlToUser.SelectedValue = null;
        tbSubject.Text = "";
        tbBody.Text = "";

        if (this.MessageSent != null)
            MessageSent(this, new EventArgs());
    }
}

MessageView.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MessageView.ascx.cs" Inherits="UserControls_Messages_MessageView_MessageView" %>
<asp:HiddenField ID="hfMessageID" runat="server" />
<div style="border-style: solid;">
    <div style="font-size: x-large; padding: 10px 10px 10px 10px;">
        <asp:Label ID="lblSubject" runat="server" Text='<%# Bind("Subject") %>' /></div>
    <div style="background-color: lightblue;">
        <table>
            <tr>
                <td>
                    <asp:Label ID="Label1" runat="server" Text="From :" />
                </td>
                <td>
                    <asp:Label ID="lblFromUser" runat="server" Text='<%# Bind("FromUser") %>' />
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Label ID="Label2" runat="server" Text="To :" />
                </td>
                <td>
                    <asp:Label ID="lblToUser" runat="server" Text='<%# Bind("ToUser") %>' />
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Label ID="Label3" runat="server" Text="Sent :" />
                </td>
                <td>
                    <asp:Label ID="lblSentDate" runat="server" Text='<%# Bind("SentDate") %>' />
                </td>
            </tr>
        </table>
    </div>
    <div style="padding: 10px 10px 10px 10px">
        <asp:Label ID="lblBody" runat="server" Text='<%# Bind("Body") %>' />
    </div>
    <div style="padding: 10px 10px 10px 10px">
        <asp:LinkButton ID="lbReply" runat="server" Text="Reply" 
            onclick="lbReply_Click" /></div>
</div>

MessageView.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class UserControls_Messages_MessageView_MessageView : System.Web.UI.UserControl
{
    public event ComposeMessageEventHandler ComposeMessage;
    public delegate void ComposeMessageEventHandler(object sender, Common.MessageComposeEventArgs e);

    public int? MessageID
    {
        get
        {
            int mid;
            return int.TryParse(hfMessageID.Value, out mid) ? mid : new int?();
        }
        set
        {
            hfMessageID.Value = (value.HasValue) ? value.ToString() : "";
            BindMessageView();
        }
    }

    private void BindMessageView()
    {
        if (MessageID.HasValue)
        {
            CustomDataViews.MessageQuery.Message message = new CustomDataViews.MessageQuery().GetMessage(MessageID.Value);

            this.Visible = true;
            lblBody.Text = message.Body;
            lblFromUser.Text = message.FromUser;
            lblSentDate.Text = message.SentDate.ToString();
            lblSubject.Text = message.Subject;
            lblToUser.Text = message.ToUser;
        }
        else
        {
            this.Visible = false;
            lblBody.Text = "";
            lblFromUser.Text = "";
            lblSentDate.Text = "";
            lblSubject.Text = "";
            lblToUser.Text = "";
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void lbReply_Click(object sender, EventArgs e)
    {
        var mess = (from m in new VAC.Data.VACDB().Messages where m.MessageID == MessageID select m);
        if (mess.Count() == 1)
        {
            if (this.ComposeMessage != null)
                this.ComposeMessage(this, new Common.MessageComposeEventArgs(mess.First().FromUserID, mess.First().Subject));
        }
    }
}

MessageBox.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MessageBox.ascx.cs" Inherits="UserControls_Messages_MessageBox_MessageBox" %>
<asp:UpdatePanel ID="up1" runat="server">
    <ContentTemplate>
        <h1>
            Message Inbox</h1>
        <cc:MessageFolder ID="mf1" runat="server" MailType="ReceivedMail" OnSelectedIndexChanged="SelectedMessageChanged"
            OnComposeMessage="ComposeMessage" />
        <h2>
            Sent Messages</h2>
        <cc:MessageFolder ID="mf2" runat="server" MailType="SentMail" OnSelectedIndexChanged="SelectedMessageChanged" />
        <br />
        <cc:MessageView ID="mv1" runat="server" OnComposeMessage="ComposeMessage" />
        <br />
        <cc:MessageCompose ID="mc1" runat="server" OnMessageSent="mc1_MessageSent" />
    </ContentTemplate>
</asp:UpdatePanel>

MessageBox.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class UserControls_Messages_MessageBox_MessageBox : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            mv1.MessageID = mf1.MessageID;
            mc1.Visible = false;
        }
    }

    protected void SelectedMessageChanged(object sender, EventArgs e)
    {
        mv1.MessageID = ((IMessageFolder)sender).MessageID;
        mc1.Visible = false;
    }

    protected void ComposeMessage(object sender, Common.MessageComposeEventArgs e)
    {
        mc1.Visible = true;
        mc1.DataBind();
        if (e.SendToUserID.HasValue)
            mc1.SendToUserID = e.SendToUserID;
        if (!string.IsNullOrEmpty(e.Subject))
            mc1.SendToSubject = e.Subject;
    }

    protected void mc1_MessageSent(object sender, EventArgs e)
    {
        mf2.Refresh();
    }
}

Messages.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Templates/Patient.master" AutoEventWireup="true"
    CodeFile="Messages.aspx.cs" Inherits="UI_Patient_Messages" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
    <cc:MessageBox ID="mi1" runat="server" />
</asp:Content>

Messages.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class UI_Patient_Messages : BasePage
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }
}
Biff MaGriff
I'm interested... can you edit your answer with the details? Thanks.
Gary
I hope that is not too overwhelming Gary.
Biff MaGriff
Not at all. It's just missing a kitchen sink!But +1 to all your posts for effort. Thanks!
Gary