2
votes

I know this question has been posted elsewhere here, but can't find a working solution. I have 2 applications: 1 is an older version of the other and runs a SignalR chat app just fine from VS2015 (using IIS Express), the other app is basically the same except for changes elsewhere (its a shopping cart), but the chat pages, scripts, and references are identical (including the path). Web.config, global.cs, startup are all identical and both run MVC5. The older app uses a standard HTML page and references the SignalR/Hubs just fine (no 404 error) with the following: Here's the agent.html page:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>LiveChat</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="../Scripts/JqueryUI/jquery-ui.css" rel="stylesheet" type="text/css" />
    <link href="assets/chat.css" rel="stylesheet" />   
    <script src="../Scripts/jquery-1.11.0.min.js" type="text/javascript"></script>
    <script src="../Scripts/jquery-ui-1.11.3.min.js" type="text/javascript"></script>
    <script src="../Scripts/json2.min.js" type="text/javascript"></script>
</head>

<body style="background-color: #fff;">
    <div class="topbar">
        <div style="float: left; margin-left: 30px;">
            <ul>
                <li><a href="/CHAT/Agent.html">Chat Agent Panel</a></li>
                <li><a href="#" id="show-real-time-visits">Show Real Time Visits</a></li>
                <li><a href="#" id="show-internal-chat">Agent to Agent Chat</a></li>
                <li><a href="/CHAT/Install.html">Configure Chat</a></li>
            </ul>
        </div>
        <div style="float: right; margin-right: 30px;">
            <ul>
                <li>Logged in as:</li>
                <li><a href="#" id="change-status">Not logged in</a></li>
            </ul>
        </div>
    </div>
    <div>
        <h2>Chat Sessions</h2>
        <div id="chat-sessions"></div>

        <div id="chat-content">
            <div id="login">
                <h3>Log In to Start Accepting Chat Requests</h3>
                <div id="login-alerts"></div>
                <fieldset>
                    <legend>Enter your agent name and password</legend>
                    <p>
                        <label>Agent Name</label>
                        <input id="login-name" type="text" class="form-control" placeholder="agent name">
                    </p>
                    <p>
                        <label>Agent Password</label>
                        <input id="login-pass" class="form-control" type="password">
                    </p>
                    <br />
                    <button id="login-btn" type="submit" class="button-1">Start accepting chat ></button>
                </fieldset>
            </div>
            <div id="agent-chat">
                <div id="real-time-visits">
                    <h2>Real time visits</h2>
                    <table id="current-visits">
                        <thead>
                            <tr>
                                <th>Visited on</th>
                                <th>Page</th>
                                <th>Referrer</th>
                                <th>City</th>
                                <th>Country</th>
                                <th>In Chat</th>
                                <th>Invite</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                </div>
                <div id="all-chatbox">
                    <div id="chatmsgsinternal" class="chat-msgs"></div>
                </div>
                <div id="chat-controls">
                    <input id="post-msg" type="text" style="width: 100%;" placeholder="Enter your text here. Enter /list for a list of available commands" />
                    <br />
                    <button id="post-btn" class="button-1" style="margin-top: 6px;">Send</button>
                </div>
            </div>
        </div>
        <div id="modal-cmd">
            <div class="modal-body">
            </div>
        </div>
    </div>

    <script src="assets/js/jquery.timeago.js" type="text/javascript"></script>
    <script src="../Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {

            $("#login-name").keypress(function (e) {
                if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
                    $("#login-btn").click();
                    return false;
                } else {
                    return true;
                }
            });
            $("#login-pass").keypress(function (e) {
                if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
                    $("#login-btn").click();
                    return false;
                } else {
                    return true;
                }
            });

        });
    </script>
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script src="agent.js" type="text/javascript"></script>
 </body>
</html>

This works fine in the old app, but the auto-generated signalr/hubs javascript is not created. The startup.cs:

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WEBCHAT.Startup))]

namespace WEBCHAT
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

ChatHub.cs (once again, it works find in the old, not the new)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Net.Mail;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace WEBCHAT
{
    public class ChatHub : Hub
    {
        private const string CONFIG_FILE = "webchat.dat";

        private static ConcurrentDictionary<string, Agent> Agents;
        private static ConcurrentDictionary<string, string> ChatSessions;

        public void AgentConnect(string name, string pass)
        {
            if (Agents == null)
                Agents = new ConcurrentDictionary<string, Agent>();

            if (ChatSessions == null)
                ChatSessions = new ConcurrentDictionary<string, string>();

            string hashPass = ToHash(pass);

            var config = GetConfig();
            if (config == null || config.Length < 2)
            {
                Clients.Caller.loginResult(false, "config", "");
            }
            else if ((config[0] == hashPass) || (config[1] == hashPass))
            {
                var agent = new Agent()
                {
                    Id = Context.ConnectionId,
                    Name = name,
                    IsOnline = true
                };

                // if the agent is already signed-in
                if (Agents.Any(x => x.Key == name))
                {
                    agent = Agents[name];

                    Clients.Caller.loginResult(true, agent.Id, agent.Name);

                    Clients.All.onlineStatus(Agents.Count(x => x.Value.IsOnline) > 0);
                }
                else if (Agents.TryAdd(name, agent))
                {

                    Clients.Caller.loginResult(true, agent.Id, agent.Name);

                    Clients.All.onlineStatus(Agents.Count(x => x.Value.IsOnline) > 0);
                }
                else
                {
                    Clients.Caller.loginResult(false, "error", "");
                }
            }
            else
                Clients.Caller.loginResult(false, "pass", "");
        }

        public void ChangeStatus(bool online)
        {
            var agent = Agents.SingleOrDefault(x => x.Value.Id == Context.ConnectionId).Value;
            if (agent != null)
            {
                agent.IsOnline = online;
                Clients.All.onlineStatus(Agents.Count(x => x.Value.IsOnline) > 0);
            }
        }

        public void EngageVisitor(string connectionId)
        {
            var agent = Agents.SingleOrDefault(x => x.Value.Id == Context.ConnectionId).Value;
            if (agent != null)
            {
                ChatSessions.TryAdd(connectionId, agent.Id);
                Clients.Caller.newChat(connectionId);
                Clients.Client(connectionId).setChat(connectionId, agent.Name, false);
                Clients.Caller.addMessage(connectionId, "system", "You invited this visitor to chat...");
                Clients.Client(connectionId).addMessage(agent.Name, "Hey there. I'm " + agent.Name + " let me know if you have any questions.");
            }
        }

        public void LogVisit(string page, string referrer, string city, string region, string country, string existingChatId)
        {
            if (Agents == null)
                Agents = new ConcurrentDictionary<string, Agent>();

            Clients.Caller.onlineStatus(Agents.Count(x => x.Value.IsOnline) > 0);

            var cityDisplayName = GetCityDisplayName(city, region);
            var countryDisplayName = country ?? string.Empty;

            if (!string.IsNullOrEmpty(existingChatId) &&
                ChatSessions.ContainsKey(existingChatId))
            {
                var agentId = ChatSessions[existingChatId];
                Clients.Client(agentId).visitorSwitchPage(existingChatId, Context.ConnectionId, page);

                var agent = Agents.SingleOrDefault(x => x.Value.Id == agentId).Value;

                if (agent != null)
                    Clients.Caller.setChat(Context.ConnectionId, agent.Name, true);

                string buffer = "";
                ChatSessions.TryRemove(existingChatId, out buffer);

                ChatSessions.TryAdd(Context.ConnectionId, agentId);
            }

            foreach (var agent in Agents)
            {
                var chatWith = (from c in ChatSessions
                                join a in Agents on c.Value equals a.Value.Id
                                where c.Key == Context.ConnectionId
                                select a.Value.Name).SingleOrDefault();

                Clients.Client(agent.Value.Id).newVisit(page, referrer, cityDisplayName, countryDisplayName, chatWith, Context.ConnectionId);
            }
        }

        public void RequestChat(string message)
        {
            // We assign the chat to the less buzy agent
            var workload = from a in Agents
                           where a.Value.IsOnline
                           select new
                           {
                               Id = a.Value.Id,
                               Name = a.Value.Name,
                               Count = ChatSessions.Count(x => x.Value == a.Value.Id)
                           };

            if (workload == null)
            {
                Clients.Caller.addMessage("", "No agent are currently available.");
                return;
            }

            var lessBuzy = workload.OrderBy(x => x.Count).FirstOrDefault();

            if (lessBuzy == null)
            {
                Clients.Caller.addMessage("", "No agent are currently available.");
                return;
            }

            ChatSessions.TryAdd(Context.ConnectionId, lessBuzy.Id);

            Clients.Client(lessBuzy.Id).newChat(Context.ConnectionId);

            Clients.Caller.setChat(Context.ConnectionId, lessBuzy.Name, false);

            Clients.Client(lessBuzy.Id).addMessage(Context.ConnectionId, "visitor", message);
            Clients.Caller.addMessage("me", message);
        }

        public void Transfer(string connectionId, string agentName, string messages)
        {
            if (!Agents.ContainsKey(agentName))
            {
                Clients.Caller.addMessage(Context.ConnectionId, "system", "This agent does not exists: " + agentName);
                return;
            }

            var agent = Agents[agentName];
            if (!agent.IsOnline)
            {
                Clients.Caller.addMessage(Context.ConnectionId, "system", agentName + " is not online at the moment.");
                return;
            }

            if (!ChatSessions.ContainsKey(connectionId))
            {
                Clients.Caller.addMessage(Context.ConnectionId, "system", "This chat session does not exists anymore.");
                return;
            }

            string currentAgentId = "";
            if (ChatSessions.TryRemove(connectionId, out currentAgentId) &&
                ChatSessions.TryAdd(connectionId, agent.Id))
            {
                Clients.Client(agent.Id).newChat(connectionId);
                Clients.Client(agent.Id).addMessage(connectionId, "system", "New chat transfered to you.");
                Clients.Client(agent.Id).addMessage(connectionId, ">>", "Starting previous conversation");
                Clients.Client(agent.Id).addMessage("", messages);
                Clients.Client(agent.Id).addMessage(connectionId, "<<", "End of previous conversation");

                Clients.Client(connectionId).addMessage("", "You have been transfered to " + agent.Name);
                Clients.Client(connectionId).setChat(connectionId, agent.Name, true);

                Clients.Caller.addMessage(connectionId, "system", "Chat transfered to " + agentName);
            }
        }

        public void Send(string data)
        {
            Clients.Caller.addMessage("me", data);

            if (ChatSessions.ContainsKey(Context.ConnectionId))
            {
                var opId = ChatSessions[Context.ConnectionId];
                Clients.Client(opId).addMessage(Context.ConnectionId, "visitor", data);
            }
            else
            {
                Debug.WriteLine("Chat Session not found.");

                var workload = from a in Agents
                               where a.Value.IsOnline
                               select new
                               {
                                   Id = a.Value.Id,
                                   Name = a.Value.Name,
                                   Count = ChatSessions.Count(x => x.Value == a.Value.Id)
                               };

                if (workload == null)
                {
                    Clients.Caller.addMessage("", "No agent are currently available.");
                    return;
                }

                var lessBuzy = workload.OrderBy(x => x.Count).FirstOrDefault();

                if (lessBuzy == null)
                {
                    Clients.Caller.addMessage("", "No agent are currently available.");
                    return;
                }

                ChatSessions.TryAdd(Context.ConnectionId, lessBuzy.Id);

                Clients.Client(lessBuzy.Id).newChat(Context.ConnectionId);

                Clients.Caller.setChat(Context.ConnectionId, lessBuzy.Name, false);

                Clients.Client(lessBuzy.Id).addMessage(Context.ConnectionId, "system", "This visitor appear to have lost their chat session.");
                Clients.Client(lessBuzy.Id).addMessage(Context.ConnectionId, "visitor", data);
            }
        }

        public void OpSend(string id, string data)
        {
            var agent = Agents.SingleOrDefault(x => x.Value.Id == Context.ConnectionId).Value;
            if (agent == null)
            {
                Clients.Caller.addMessage(id, "system", "We were unable to send your message, please reload the page.");
                return;
            }

            if (id == "internal")
            {
                foreach (var a in Agents.Where(x => x.Value.IsOnline))
                    Clients.Client(a.Value.Id).addMessage(id, agent.Name, data);

            }
            else if (ChatSessions.ContainsKey(id))
            {
                Clients.Caller.addMessage(id, "you", data);
                Clients.Client(id).addMessage(agent.Name, data);
            }
        }

        public void CloseChat(string id)
        {
            if (ChatSessions.ContainsKey(id))
            {
                Clients.Client(id).addMessage("", "The agent close the chat session.");

                string buffer = "";
                ChatSessions.TryRemove(id, out buffer);
            }
        }

        public void LeaveChat(string id)
        {
            // was it an agent
            var agent = Agents.SingleOrDefault(x => x.Value.Id == id).Value;
            if (agent != null)
            {
                Agent tmp = null;
                if (Agents.TryRemove(agent.Name, out tmp))
                {

                    var sessions = ChatSessions.Where(x => x.Value == agent.Id);
                    if (sessions != null)
                    {
                        foreach (var session in sessions)
                            Clients.Client(session.Key).addMessage("", "The agent was disconnected from chat.");
                    }

                    Clients.All.updateStatus(Agents.Count(x => x.Value.IsOnline) > 0);
                }
            }

            // was it a visitor
            if (ChatSessions.ContainsKey(id))
            {
                var agentId = ChatSessions[id];
                Clients.Client(agentId).addMessage(id, "system", "The visitor close the connection.");
            }
        }

        public override Task OnDisconnected(bool stopCalled)
        {
            return Clients.All.leave(Context.ConnectionId);
        }

        public void SendEmail(string from, string message)
        {
            if (ValidateEmail(from) == true)
            {
                var msg = new MailMessage();
                msg.To.Add(new MailAddress(from));
                msg.Subject = "WEBCHAT - Offline Contact";
                msg.Body = "You received an offline contact from your WEBCHAT chat widget.\r\n\r\n" + Nop.Core.Html.HtmlHelper.FormatText(message, true, false, false, false, false, false);

                try
                {
                    string emResult = eMail.SendEmail(msg.Subject, msg.Body, from, "[email protected]", "mail.xxx.com", "[email protected]", "password", from, "My Chat Agent", false, null, 25, from, MailPriority.High, DeliveryNotificationOptions.None);
                }
                catch
                {

                }

            }
        }

        #region Install and config methods
        public void getInstallState()
        {
            var config = GetConfig();

            if (config != null && config.Length >= 2)
                Clients.Caller.installState(true, config[0]);
            else
                Clients.Caller.installState(false, "chattemptoken");
        }

        public void AdminRequest(string pass)
        {
            var config = GetConfig();

            if (config != null && config.Length >= 2)
            {
                if (config[0] == ToHash(pass))
                    Clients.Caller.adminResult(true, config[0]);
                else
                    Clients.Caller.adminResult(false, "");
            }
            else
                Clients.Caller.adminResult(false, "");
        }

        public void SetConfig(string token, string adminPass, string agentPass)
        {
            bool shouldSave = false;
            var config = GetConfig();

            if (config != null && config.Length >= 2)
            {
                if (config[0] == token)
                    shouldSave = true;
            }
            if (token == "chattemptoken")
                shouldSave = true;

            if (shouldSave)
            {
                string configPath = HttpContext.Current.Server.MapPath("~/App_Data/" + CONFIG_FILE);

                File.WriteAllText(
                    configPath,
                    ToHash(adminPass) + "\n" + ToHash(agentPass));

                Clients.Caller.setConfigResult(true, "Config file updated.");
            }
            else
                Clients.Caller.setConfigResult(false, "Unable to save the config file.");
        }

        private string GetCityDisplayName(string city, string region)
        {
            var displayCity = string.Empty;
            if (!string.IsNullOrEmpty(city))
            {
                displayCity = city;
                if (!string.IsNullOrEmpty(region))
                {
                    displayCity += ", " + region;
                }
            }
            return displayCity;
        }

        private string[] GetConfig()
        {
            string configPath = HttpContext.Current.Server.MapPath("~/App_Data/" + CONFIG_FILE);
            if (File.Exists(configPath))
            {
                return File.ReadAllLines(configPath);
            }
            return null;
        }

        public string ToHash(string password)
        {
            if (string.IsNullOrEmpty(password))
                return "";

            var provider = new SHA1CryptoServiceProvider();
            var encoding = new UnicodeEncoding();
            return Convert.ToBase64String(provider.ComputeHash(encoding.GetBytes(password)));
        }
        #endregion

        public bool ValidateEmail(string strCheck)
        {
            try
            {
                System.Net.Mail.MailAddress vEmailAddress = new System.Net.Mail.MailAddress(strCheck);
            }
            catch (Exception ex)
            {
                //_logger.Error(string.Format("Error validating e-mail. {0}", ex.Message), ex);
                return false;
            }
            return true;
        }
    }

    public class eMail
    {
        public static string SendEmail(string emailSubject, string emailBody, string senderAddress, string recipientAddress, string emailServer, string accountName, string accountPassword, string senderName = "", string recipientName = "", bool isHTML = true, List<string> attachmentPaths = null, int portNumber = 25, string replyToAddress = "", System.Net.Mail.MailPriority priority = MailPriority.Normal, DeliveryNotificationOptions deliveryNotification = DeliveryNotificationOptions.None)
        {
            //this will return a string of "success", if successful and an error message if not
            string retval = string.Empty;
            if (senderName.Length == 0)
            {
                senderName = senderAddress;
            }
            if (recipientName.Length == 0)
            {
                recipientName = recipientAddress;
            }
            MailMessage msg = new MailMessage(new MailAddress(senderAddress, senderName), new MailAddress(recipientAddress, recipientName));
            SmtpClient mailClient = null;
            mailClient = new SmtpClient(emailServer, portNumber);

            msg.Subject = emailSubject;
            msg.IsBodyHtml = isHTML;
            msg.Body = emailBody;

            if (attachmentPaths != null)
            {
                foreach (var attPath in attachmentPaths)
                {
                    if (File.Exists(attPath))
                    {
                        msg.Attachments.Add(new Attachment(attPath));
                    }
                }
            }

            if (accountName.Length > 0 && accountPassword.Length > 0)
            {
                System.Net.NetworkCredential userSMTPAccount = new System.Net.NetworkCredential(accountName, accountPassword);
                mailClient.UseDefaultCredentials = false;
                mailClient.Credentials = userSMTPAccount;
            }
            else
            {
                mailClient.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
            }

            msg.DeliveryNotificationOptions = deliveryNotification;
            try
            {
                mailClient.Send(msg);
                retval = "success";
            }
            catch (SmtpException ex)
            {
                retval = ex.Message;
            }
            catch (Exception ex2)
            {
                retval = ex2.Message; //for general errors
            }
            finally
            {
                msg.Dispose();
            }

            return retval;
        }



    }

}

There's more, all of the code works fine on the old version, but not on the new. All the required DLLs are in the BIN folder. The package.config is identical (except an older version of OWIN - which may be suspect). I just don't know where else to look for a difference that would explain why the old version of the Application works and the new one does not (everything loads in the new one, scripts, css, pages, etc. except the dreaded SignalR/Hubs). Is there a setting somewhere that I missed?

1

1 Answers

3
votes

OMG! I knew the answer had to be dumb simple! It is! First, I upgraded OWIN security to the latest version (not sure if this was the real problem). Then, I saw a new key in the Web.config file (or I missed it last time) called:

<appSettings>
   <add key="owin:AutomaticAppStartup" value="true" />
    ...
</appSettings>

I changed the value from false to true and now it works. After 3 days of code tweaking and trial and error by looking at other forum posts, I'm glad it was this simple. Hope this helps others. What bugs me, however is that there is no such setting in the web.config in the old application (go figure) - it worked without it.