It’s difficult to locate where a certain functionality is implemented in a code base that is unfamiliar to us, and even if you are familiar with the code base, when a project grows in size (imagine 3+ years), it inevitably becomes harder to manage. Locating code takes more time, and it stops being obvious where a certain piece of functionality is implemented.
In this blog post I’ll share with you a set of tips and tricks to help you go from web page, to JavaScript code, to server side code that is responsible for rendering that page.
For the server side I’ll focus on ASP.NET MVC, which is my cup of tea. If you use any other framework I still believe they’re applicable, and if you know how to realise them in your framework of choice please feel free to chime in the comments.
Tip 1. Starting point: The Url
The Url gives a lot of information, and it is a good starting point. For example, if your web app is build using ASP.NET MVC you could take from a Url like this: http://blinkingcaret.com/Account/Login
that the code that renders the page is in a Controller named AccountController
, and an action (method) named Login
.
The default view name in this scenario would be Login.cshtml
, the same name as the action method, however this can be overridden, and the view name and it’s location can actually be arbitrary.
Speaking of defaults, even though the default behaviour in this scenario is that the controller and action name are Account and Login, respectively, this can also be overridden. MVC frameworks use the concept of a router, which is a component responsible extracting from the Url which controller and action to invoke. It is possible to specify a route other than the default one (and that route can be “Account/”) and specify an arbitrary default controller.
So, even though the Url is a good starting point, most frameworks provide mechanisms to override the default behaviour of how a Url is ultimately translated into the code that renders the view. Tip 7 provides an alternative that, at least for ASP.NET MVC, will give you the right controller and view every time (it might still be applicable to other frameworks if they have similar extensibility points).
Tip 2: Chrome developer tool’s inspector (Elements tab) for HTML/CSS
Chrome developer tools offer excellent help in finding code. They are accessible by pressing F12 (or Control+Shift+I for those used to use Firefox’s Firebug).
One straightforward way to take advantage of the dev tools to find where the code that generates the markup is located is to just inspect an element, find the snippet of markup you are interested in and then use Find in Files functionality of the IDE. I wouldn’t recommend this approach too often though. On big projects the search tends to take a reasonable amount of time, and we all know that more than 5 seconds is enough to create the temptation to open a new tab and go read random stuff on the internet. Again, tip 7, where applicable, is a better alternative.
Tip 3: Markup injected in the page via Ajax
This tip is for when there is content being injected in the page via Ajax, and you want to quickly find where is the JavaScript code responsible for it.
You can set chrome dev tools to break on “Sub tree modifications”. If you do this Chrome will break every time anything gets added or removed from the DOM and then you can follow the callstack until you find the code responsible for injecting the html in the page.
Tip 4: Plain JavaScript events
It is often the case that we need to find what JavaScript is running in response to a user’s action, for example, the click of a button, or a change in a dropdown.
If the event was registered using plain JavaScript (i.e. not with jQuery), chrome dev tools has a tab, named Event Listeners, that list all the event handlers registered for the element that is selected.
When you are using jQuery to register events, these won’t show up in the Event Listeners tab though. It is jQuery itself that handles these events. That leads us to:
Tip 5: jQuery events
If an event was registered using jQuery and we want to check which code is running in response to it we’ll have to use jQuery’s api.
The data regarding which events are registered for which element is accessible by using the $._data
method. For example, if you want to check which jQuery events are registered in the element with id="myElement"
you could write this in the console:
$._data($('#myElement').get(0), "events")
Or just take advantage of the fact that in chrome dev tools the element you select in the elements tab is accessible in the console by writing $0
.
Every time you get chrome to show you a function handler in the console you can right click on it and select Show function definition and that will take you to the code that runs in. The code will open in a new tab with the same name as the file name that contains the code. You can even add breakpoints and step through the JavaScript code if required.
In jQuery there’s the notion of delegated events, where you define an event handler in a parent element and you specify a selector for the child elements. When an event is raised on a child element that matches the selector on the parent, the event handler runs, for example:
$('div#container').on("click", ":button", function(e) { console.log(e.target.id + " was clicked");});
This event handler will run every time a button is clicked inside div with id=”container”. This is very useful if the contents of the div are injected via ajax. You don’t have to register handlers for the elements inside the div this way. This has the disadvantage of making finding those handlers hard.
I’ve made a tool named findHandlersJs that will find all the jQuery event handlers that run in an element, it’s syntax is very simple findEventHandler(eventName, element(s))
, for example:
findEventHandlers("click", "div#container :button")
or if you have the element selected in the Elements tab in chrome, you can simply write:
findEventHandlers("click", $0)
It returns information about not only any event handler registered in the buttons inside div with id=”container” but also any delegated event handlers that run in response to a click in that button(s).
Tip 6: Client side frameworks (KnockoutJS and AngularJS)
It is possible to access a lot of information from the console even if you are using client-side frameworks such as angular or knockout to generate your client side markup.
The examples here are only for Angular and Knockout because these are the ones that I’m more familiar with, I’m sure there are equivalent methods of getting to this information with other frameworks. If you have some tips for other frameworks, feel free to share in the comments.
For KnockoutJS it is possible to get to the ViewModel, and from there you have access to the functions that run in response to the user’s actions. For example if you want to debug the code that runs in response to the user clicking a button:
You can use the ko.dataFor(domElement)
to get the ViewModel that was used. In the ViewModel you can not only access the observables, but also all the functions that are invoked in response to users’ actions. Alternatively, you can use ko.contextFor
to access the specific binding context
of the element.
For AngularJS, it is similar, you use the angular.element(domElement).scope()
to access the angular scope for the element. From the scope you can then go to the functions that are invoked in response to the user’s actions.
Tip 7: Leverage the extensibility points of the Framework that you are using to inject debug information when rendering the pages
Even though this example is for ASP.NET MVC, this should be applicable to any framework that has similar extensibility points.
ASP.NET MVC renders the views through a view engine, and the framework has extensibility points for new view engines to be added, in fact, out of the box it comes with two view engines, one for WebForms (.aspx) and another for Razor views (.cshtml).
I’m going to show you how you can create your own version of the Razor View Engine that adds extra debug information as html comments to the pages that are rendered.
Comments are a better solution, than say for example, wrapping a partial view inside a div
with attributes because doing so could potentially interfere with css rules. Html comments are innocuous, and are rendered in a different colour in chrome dev tools which makes them easy to spot.
First you’ll have to create a new view engine class, for example RazorDebugViewEngine
, that inherits from RazorViewEngine
.
Next override the method that creates the (full) views (CreateView
) or the one that creates partial views (CreatePartialView
), or both.
We’ll override both in this example and will provide different comments for each.
Usually RazorViewEngine
returns an instance of RazorView
from the Create methods. We’ll provide our own RazorDebugView
that we will define shortly:
public class RazorDebugViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new RazorDebugView(controllerContext, viewPath,
layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new RazorDebugView(controllerContext, partialPath,
layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
{
IsPartialView = true
};
}
}
I had to check the source code for MVC to figure out how the RazorViews were being created, it’s really great that this is open source :).
The IView
interface (that RazorView
implements) defines the contract for the Render method (void Render(ViewContext viewContext, TextWriter writer);
) which is what ultimately renders the view. We are going to hijack that process by having the view render to a StringWriter
instead so that we can access the generated html as a string.
We’ll add extra information, available from the ViewContext
, to the html that was generated. Then we let the process progress as it would have otherwise by writing the generate content plus the debug information to the original TextWriter
.
We’ve also added a property named IsPartialView
to our RazorDebugView
so that we can easily distinguish from a partial or full view.
Here’s how that looks like:
public class RazorDebugView : RazorView
{
public bool IsPartialView { get; set; }
public RazorDebugView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions)
: base(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions)
{
}
public RazorDebugView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator)
: base(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions, viewPageActivator)
{
}
public override void Render(ViewContext viewContext, TextWriter writer)
{
StringWriter stringWriter = new StringWriter();
base.Render(viewContext, stringWriter);
var renderedHtml = stringWriter.GetStringBuilder().ToString();
string startDebugInfo;
string endDebugInfo;
if (IsPartialView) {
var debugText = string.Empty;
if (viewContext.IsChildAction)
debugText = string.Format("ChildAction: controllerName: {0}, actionName: {1}, viewPath: {2}", viewContext.Controller.GetType(), viewContext.RouteData.Values["action"], ViewPath);
else
debugText = string.Format("viewPath: {0}", ViewPath);
startDebugInfo = string.Format("\n<!-- BeginPartialView {0} -->\n", debugText);
endDebugInfo = "\n<!-- EndPartialView -->\n";
}
else
{
startDebugInfo = string.Format("\n<!-- BeginView controllerName: {0}, actionName: {1}, viewPath: {2} -->\n", viewContext.Controller.GetType(), viewContext.RouteData.Values["action"],ViewPath);
endDebugInfo = "\n<!-- EndView -->\n";
}
writer.Write(startDebugInfo + renderedHtml + endDebugInfo);
}
}
To use this new ViewEngine
we have to add it. The best place to do it is in Global.asax
. You can use the #if
compiler directive so that you only use this view engine in debug mode. That would look like this:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//...
#if DEBUG
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorDebugViewEngine());
#endif
}
}
Here’s a page using this new view engine would look like in chrome dev tools:
That’s it. Hope these tips are useful. If you have more please share in the comments.