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.


Migrating a TypeScript application from ASP.Net MVC to Angular2 – Lessons Learned

April 26, 2016

To evaluate Angular2, I started to convert a TypeScript application hosted in an ASP.Net MVC application to be hosted by the Angular framework.

The application continuously polls data from a web service, performs a couple of calculations, and displays the calculated results. It is a display-only auto-paging application Рthe only user interaction being to allow for manual paging. Parts of the display are updated by requesting a PartialView and adding it to the display using jQuery append().

Getting started is quite easy:

  • download, install, and configure Visual Studio Code
  • download and install node.js
  • create application directory and configuration files, and run npm install

Components

First, we need to translate MVC Controllers and Views into Angular2 @Component classes and templates:

A page (controller+view) typically translates into a component with an associated HTML template.

Depending on your application, a PartialView may also be represented by a component. It may also translate into a part of a template being activated by NgSwitch or NgIf.

The file app/app.component.ts defines the URL routes, with @RouteConfig being the equivalent of MVC routing configuration. Each page you can directly address by a URL in a browser needs to be added to the @RouteConfig declaration.

Angular Exceptions

During development, you will occasionally meet the error condition 

The selector ‚Äúmy-app‚ÄĚ did not match any elements

Currently, the only reliable way for me to resolve the condition is to close the browser window, restart npm lite-server, and hitting ctrl-F5.

Unfortunately, this was not the only (recurring) exception generated by the Angular2 framework. True, this is still a beta version (15), but if an exception in the framework occurs, there is almost no way of debugging it, as developers are used to debug their code. There is simply too much magic happening in the background. I repeatedly had to remove code or html and re-insert it step by step to figure out which part of the code caused a framework exception. And judging from browsing through GitHub issues, the situation has improved considerably from earlier versions.

Coding and Legacy Code

If your component needs to process URL parameters, there is the built-in routeParams.get() method. There is no automatic mapping of URL parameters to model class properties, but I developed a simple TypeScript function which will provide this convenience.

Adding and referencing existing TypeScript code to an Angular2 application is straight-forward. As I wanted to keep changes to existing code to a minimum, I simply added the scripts in the main index.html page using <script> tags.

However I found that Angular2 does not render <script> elements inside HTML templates. Fortunately, this code can be moved to and thus accessed from the @Component class, either in its constructor or one of the component’s lifecycle hooks, the Angular equivalent of ASP.Net events.

Angular2 uses import to load and reference TypeScript code. To reference code loaded by a <script> tag, you need to declare external symbols, as¬†is typical for TypeScript’s .d.ts files.

To implement calls from the legacy code into the Angular2 code, I needed to store the Angular component in a window property, and access this property to invoke a method of that component:

// myComponent.ts:
constructor() {
  (<any>window).myComponent = this;
}
methodCall() {
  ..implementation..
}
// legacy.ts
(<any>window).myComponent.methodCall();

Just a tiny change. Alternatively, every referenced declaration (and their dependencies) would have needed to be marked as export.

Comparing Architecture and TypeScript Hosting in ASP.Net MVC and Angular2

Comparing Architecture and TypeScript Hosting in ASP.Net MVC and Angular2

Dynamic Views

To dynamically render additional views, I created another component and assigned it a selector. The parameters displayed in the dynamic view are stored in a class, and the main component stores an array of this class:

export class MySubView {
  ... members ...
}

In the main component:

public subviews: MySubView[] = [];

In the main component’s HTML template:

<mysubview *ngFor="#subview of subviews" [data]="subview"></mysubview>

The sub component is declared as

@Component({
 selector: 'mysubview'
}
export class MySubViewComponent {
  @Input() data: MySubView;
}

A new mysubview item is simply created by Angular2 by adding an object to the array:

this.subviews.push(new MySubView(...parameters..));

If a component needs to dynamically¬†switch between several available templates, things get a bit complicated, but it’s feasable.

Disabling some Angular2 features

Angular2 allows CSS styles per @Component, and when rendering a component’s CSS, adds a component-specific attribute to the HTML elements, as well as to their CSS declarations.

Since I did not want to edit my CSS (which would have certainly made the CSS declarations cleaner and more specific), I deactivated this CSS magic by declaring the component

encapsulation: ViewEncapsulation.None,

Also, I wanted to disable Angular’s data-binding mechanism, since my legacy code takes care of all data-binding, and I wanted to avoid any interference between the two.

Adding the component declaration

changeDetection: ChangeDetectionStrategy.OnPush

switches change detection and auto-updates off.

Conclusion

Starting with Angular2 certainly comes with¬†a learning curve. Switching platforms, even though they are comparable, may be a major effort.¬†Whereas my little sample just concerned¬†hosting existing functionality in Angular2, adapting existing code¬†as a “real” Angular2 application – designing and using components, CSS handling, data-binding and change detection – is probably an¬†effort similar to writing the application from scratch.

Resources