Solving SignalR $.connection.HubName is null or undefined

July 5, 2017

I added support for SignalR to one of the ASP.Net MVC applications I develop, mainly following this Microsoft tutorial.

I was surprised by the fact that you cannot install the SignalR package from the NuGet GUI (in VS 2013 at least), but need to switch to the Package Manager Console and type

install-package Microsoft.AspNet.SignalR

It worked nevertheless.

I also copied the JS initialization code from there

 $(function () { 
    // Declare a proxy to reference the hub. 
    var chat = $.connection.chatHub;
    chatHub.client.broadcastMessage = function (name, message) { ... };
}

(adjusted, of course, to the hubs defined in my project), only to find that the JavaScript console in Chrome Developer Tools displays the error message

Uncaught TypeError: Cannot read property 'client' of undefined
 at HTMLDocument. (38024:10393)
 at fire (jquery-1.12.4.js:3232)
 at Object.fireWith [as resolveWith] (jquery-1.12.4.js:3362)
 at Function.ready (jquery-1.12.4.js:3582)
 at HTMLDocument.completed (jquery-1.12.4.js:3617)

The error is raised by the line

chatHub.client.broadcastMessage = function (name, message) {

with chatHub being undefined.

I stepped through the JavaScript code and found that the (automagically generated) JavaScript file

< script src="@Url.Content("~/signalr/hubs")" >< /script>

defines the hubs correctly, but somehow the hub definitions are lost when the

$(function() {

is being executed.

It turned out to be a question of JavaScript scopes, and I moved the client callback definition out of the jQuery $(function(){}) directly into the


Debugging Ajax’ed JavaScript and jQuery val() calls

June 8, 2017

I develop a web application which displays data in a read-only form, and loads the edit form upon pressing a button

$(function () {
  $(".btnEdit").click(function () {
    $.ajax({
      url: '@Url.Action("Form", new { id = Model.ID })',
      type: 'post'
    });
  });
});

Now I needed to debug the JavaScript code loaded by the call to $.ajax(), but Chrome does not seem to display the loaded response in its tree of Sources.

An answer on SO provided me with the solution: Simply add the line

//# sourceURL=@Url.Action("Form", new { id = Model.ID })

inside a <script> block in the AJAX-loaded HTML. This will add the requested URL under the (no domain) node

chrome ajax js

Now that I have access to the source file, I needed to find all invocations of the jQuery val() function, since I was tracking down a wrong value in an <input>.

Again SO provided a solution, which I added to my code

(function($){
  var originalVal = $.fn.val;
  $.fn.val = function(){
    var selectorPath = $(this).parents().map(function () {return this.tagName;}).get().reverse().join("->");
    console.log("val( #" + $(this).attr("id") + " " + selectorPath + " , " + JSON.stringify(arguments) + ")");
    var result =originalVal.apply(this,arguments);
    return result;
  };
})(jQuery);

Now that the calls to val() were logged to the Console, it was easy to find where the wrong value was set.


Adding Dropdown Arrow for Chosen Plugin in Multiselect Mode

April 2, 2017

I use the jQuery Chosen plugin in a web application, and recently got the request to display a dropdown arrow in multiselect mode.

I found an answer on SO regarding styling of the dropdown arrow, but that covered only singleselect mode. At least a starting point 😉

Within minutes, I had a CSS-only solution for this particular problem:

<style type="text/css">
.chosen-container-multi.chosen-container .chosen-choices::after {
    background: url(@Url.Content("~/Content/chosen-sprite.png")) no-repeat 3px 4px;
    width: 16px;
    height: 100%;
    content: " ";
    position: absolute;
    right: 0;
    background-color: lightgray;
}
</style>

(Note that this is an ASP.Net MVC application, and @Url.Content() computes the URL relative to application root.)

Only after I solved this, I found that there is a closed (and unresolved) issue on github from 2011, with comments up to 2016, but no solution to a valid feature request…


Emoticon Selection in ASP.Net MVC

March 30, 2017

I had to implement a good/bad/neutral selection UI in an ASP.Net MVC application.

This simple task turned out to be rather cumbersome, given different browsers implementing different things differently, and the MVC framework restricting access to generated markup.

I first thought about a DropDownList (i.e.) rendering a set of emoji, only to find

  • you cannot pass a C# emoji as SelectListItem.Text
new SelectListItem {Text = "\u1F60A" }
  • you cannot add the emoji HTML entity 😊 astext (at least not in Chrome)
  • you cannot add a @class parameter to SelectListItem
  • you cannot add a background-image:url() definition to an
  • you cannot add HTML tags inside an(

    )

However, I found code to overcome the limitations of SelectListItem, either by copying and extending code from the MS MVC framework, or by XML manipulation of the built-in HTML generator.

Maybe the dropdown list was just the wrong path to follow, so I searched for ways to style radiobuttons, and found this nice demo on codepen.

I modified the demo to fit my needs, and voilà, here’s my first codepen:

A radiobutton-based emoji selector

Update: Apparently, IE does not render an <img> inside a <label> clickable, unless you specify pointer-events: none.


Problems using anti-forgery token

January 27, 2017

ASP.Net MVC provides an anti-forgery mechanism using the methods @Html.AntiForgeryToken() and the [ValidateAntiForgeryToken] attribute.

I developed a web application using this mechanism for login, as the MVC template automatically provides this code. It worked fine using Chrome, but when I tried out the application in Internet Explorer, a couple of errors occurred relating to it:

Anti-forgery token is meant for user “” but the current user is “username”

The provided anti-forgery token was meant for a different claims-based user

The different answers on SO and various blogs offered no solution:

  • setting AntiForgeryConfig.SuppressIdentityHeuristicChecks to true
  • exchanging the anti-forgery token
  • changing AntiForgeryConfig.UniqueClaimTypeIdentifier

The solution that finally worked destroys the current ASP.Net session and signs out if the user is currently logged in:

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
  Session.Abandon();
  if (AuthenticationManager.User.Identity.IsAuthenticated)
    AuthenticationManager.SignOut();
  ViewBag.ReturnUrl = returnUrl;
  return View();
}

Viewing ELMAH Logs of multiple applications

January 26, 2017

I recently added ELMAH and ELMAH.Mvc to a couple of web applications, and configured them to log into the same MSSQL database.

The table ELMAH_Error distinguishes the source of error in the columns Host (storing the host name of the web application) and Application (storing the IIS Configuration Path of the web application, for explanations see e.g. here).

By default, the /elmah handler only displays the error messages of the current application.

However, I wanted to see the errors of all ELMAH-enabled applications.

After some research into the C# code and the MSSQL database, the solution is surprisingly simply:

  • Create a new database, e.g. “elmah-read”
  • Create synonyms to the original table and the logging SP (we won’t really need this SP)
CREATE SYNONYM ELMAH_Error FOR Elmah.dbo.ELMAH_Error 
GO
CREATE SYNONYM ELMAH_LogError FOR Elmah.dbo.ELMAH_LogError 
GO
  • Copy the stored procedure [dbo].[ELMAH_GetErrorXml] and remove the @Application from the WHERE clause
WHERE [ErrorId] = @ErrorId
 --AND [Application] = @Application
  • Copy the stored procedure [dbo].[ELMAH_GetErrorsXml], removing the @Application from the WHERE clauses, and extending the selected [host] column
 SELECT 
  errorId = [ErrorId], 
  application = [Application],
  host = [Host] + ' ' + 
    RIGHT( [Application], CHARINDEX( '/', REVERSE( [Application] ) + '/' ) - 1 ),
  • Create an empty ASP.Net Web Application in VS, add Elmah Core Library and Elmah.Mvc from NuGet, and configure the connection string as in the original web applications
  • To allow remote access to the log viewer, add in web.config:
<elmah>
      <security allowRemoteAccess="true"/>
</elmah>
  • Don’t forget to properly configure the Authentication feature of the log viewer application in IIS
  • Run

TypeScript logging with log4javascript in an ASP.Net MVC application

July 12, 2016

I needed to add logging functionality to an existing TypeScript application hosted on ASP.Net MVC.

Since I usually use log4net in C#, log4javascript was an obvious choice, as it provides a similar API.

First, I needed to declare the parts of the log4javascript API I use in TypeScript:

declare module log4javascript {
    class Logger {
        info(msg: string);
        info(msg: string, value: any);
        addAppender(app: AjaxAppender);
    }
    function getLogger(name: string): Logger;
    function getNullLogger(): Logger;
    class AjaxAppender {
        constructor(url: string);
        setSessionId(id: string);
        setLayout(layout: JsonLayout);
        addHeader(key: string, value: string);
    }
    class JsonLayout {
        constructor(readable: boolean, combine: boolean);
    }
}

In the logging class, I added logger and AjaxAppender. Depending on the data to log, I also configure a JsonLayout.

logger: log4javascript.Logger;

A non-logging logger instance is created using getNullLogger():

this.logger = log4javascript.getNullLogger();

Plain-text AjaxAppender

Use these statements to create an Ajax-based logger in TypeScript:

this.logger = log4javascript.getLogger("MyLoggerName");
var app = new log4javascript.AjaxAppender("http://myhost/myapp/mylogger");
this.logger.addAppender(app);

On the MVC side, the controller receives the parameters: logger, timestamp, level, url, message, sessionid, and layout. (Note that these parameter names can be changed using the setKeys() method)

The value for sessionid is set using

ajaxAppender.setSessionId(sessionId);

To log a string message, simply write

this.logger.info("my info log message");

The C# controller method stub therefore looks something like this:

[ValidateInput(false)]
[HttpPost]
public ActionResult Log(long? timestamp, string level, string sessionid, string message)
{
    var ipaddr = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    if (string.IsNullOrEmpty(ipaddr))
        ipaddr = Request.ServerVariables["REMOTE_ADDR"];
    if (string.IsNullOrEmpty(ipaddr))
        ipaddr = Request.UserHostAddress;

    if (level == "INFO")
        logger.Info(ipaddr + " " + sessionid + " " + message);
    else
        logger.Debug("[" + level + "] " + ipaddr + " " + sessionid + " " + message);
    return Json(null);
}

where logger is an instance of log4net.Ilog.

JSON-formatted AjaxAppender

To change the appender’s data format to JSON, we set its layout to JsonLayout:

this.logger = log4javascript.getLogger("MyJsonLogger");
var app = new log4javascript.AjaxAppender("http://myhost/myapp/myjsonlogger");
app.setLayout(new log4javascript.JsonLayout(false, false));
app.addHeader("Content-Type", "application/json");
this.logger.addAppender(app);

The C# controller method receives a JSON array of log entries

    [
        {
            "logger":"MyJsonLogger",
            "timestamp":1448621329758,
            "level":"INFO",
            "url":"http://localhost/myapp/...",
            "message":
            [    "message1",
                "message2", ...
            ]
        }
    ]

where message1, message2, etc are the parameters of the logger’s debug/info/warn etc. calls.

We create a C# class containing the required properties

public class Data
{
    public string logger;
    public long? timestamp;
    public string level;
    public string url;
    public string[] message;
}

and deserialize the HTTP request’s InputStream using JSON.Net:

[ValidateInput(false)]
[HttpPost]
public ActionResult Execute()
{
    string inputContent;
    using (var sr = new StreamReader(HttpContext.Request.InputStream))
    {
        inputContent = sr.ReadToEnd();
    }
    var data = JsonConvert.DeserializeObject<Data[]>(inputContent);

    if (data != null)
    {
        foreach (var d in data)
        {
            if (d.level == "INFO")
                logger.Info(d.message[0] + " " + d.message[1]);
            else
                logger.Debug("[" + d.level + "] " + d.message[0] + " " + d.message[1]);
        }
    }
    return Json(null);
}

Again, logger is an instance of log4net.Ilog.