mo.notono.us

Thursday, March 24, 2011

IE9 the new king of the Underscore performance tests

See http://documentcloud.github.com/underscore/test/test.html and past tests: http://mo.notono.us/search?q=underscore

Labels: , , , ,

Wednesday, September 15, 2010

IE9 Beta test scores against Underscore.js

Another new browser launch, and another obligatory proves-absolutely-nothing-definite/just-a-single-use-case performance test against the Underscore.js utility framework.

Previous tests showed that IE was gaining on the lead (Chrome), and that is still the case: As seen in the charts below, IE is sometimes faster, but still generally slower than Chrome (longer bars are better):

Again, this test proves very little, other than that IE9’s new Chakra JS engine is still slower than V8 for doing array iterations, and faster for mapping, getting property values, and creating list ranges.  IE9 has a number of features Chrome doesn’t have (yet) such as hardware acceleration (the IE Speed reader demo runs 790% faster in IE than in Chrome!) and ES property getter/setter standard compliance, just to mention two random ones…

IE9 beta is a HUGE step forward for IE.  Not sure if it will become my default browser, but this is at great day for the web.  Now if the EU and other governing bodies can just look the other way while MS quietly replaces all prior IE instances with IE9… ;-)

Labels: , , , ,

Saturday, June 26, 2010

Undersore.js Performance Tests Revisited (this time with pretty charts)

Out of sheer vanity, I added my own blog feed to my Google Reader, as I was curious if anyone ever Liked my posts.
Answer: Nope. :-(

Anyhow, I came across my post on Underscore.js, and since MS just dropped Platform Preview 3 of IE 9, I thought I’d redo the comparison in Chrome6, IE8 and IE9 (though I know this is hardly any complete benchmark test, it’s still telling).  The results are below: 

As I said in my last post, I can’t wait for IE9 to replace every previous IE version…  I haven’t been this excited about an IE product since IE4, which was more than 10 years ago.

IE 8 – still a dog.

IE8 results

IE 9 PP3 – Starting to look good!  Faster than Chrome in some tests!

IE9 results

Chrome 6 – still the winner in most categories, though the lead is shrinking

Chrome 6 results

Bottom line, though – if you’re doing a lot of looping/mapping, you should use Underscore rather than jQuery.

Labels: , , , , ,

Monday, August 16, 2004

Brief GhostDoc v. 1.0.2 review

Roland Weigelt has an excellent little add-in to VS.NET, called GhostDoc. It recently won 1st price in Roy's add in contest, and it was well deserved. I downloaded it last week and tried it out on our current project, and I can see that it has great potential but falls short in a few areas. I mentioned that on Roland's blog, and he asked me for further feedback which I provided by email - but thought I'd also share here:

Mostly the method names that GhostDoc has trouble resolving are “bad” names that I had chosen. This is to be expected - it's a simple parser, not HAL - and in some cases GD actually helped point these instances out to me. But it also has problems with common composite-action styled method names like DataBind – “Datas the bind. “ wasn’t exactly what I was looking for (“Databind” works fine of course).

Where it really falls short is when I use prefixes to separate and organize methods – for example I have a common web service that exposes web methods from multiple business rule classes – in this case I use the original class name as the prefix followed by an underscore – e.g. Company_SearchParent. For this GD returns “Company_s the search parent.”

What I’d like to see is some way to set filters for (regular) expressions that GD should match, as well as filters that it should NOT match. Along with the excellent filters you have already included, that should make it capable of handling most anything (in English at least). That way I could say – exclude prefixes that end in _ so in the above example GD would just see SearchParent, which it handles quite nicely: “Searches the parent.”

Actually, come to think of it, I believe GD needs to have some special setting for underscores to handle the default names of event handlers: for example, my listDetail control has an InitializeLayout event; so VS.Net generates the method name listDetail_InitializeLayout. The expected summary of this method would be “Initializes the layout of the listDetail.” But GD generates “Lists the detail_ initialize layout.”. If GD was set to by default treat anything preceding an underscore as the indirect object of an action, that would solve both this situation and that above – I would get “Searches the parent of the Company” which is exactly what I’m doing. But some kind of regex filters would still be great…

Additionally, there needs to be user changeable settings for common terms: the term ID is one I’d like to keep as ID; i. d. looks strange to me. Similarly, an event handler’s EventArgs parameter, which VS.NET by default names e, ought to be trapped and expanded on (of course here you run into potential conflicts with Exceptions, which some people also give the name e – but that is why I use exc for Exceptions…).

Overall, this is a great tool, but I think these changes would make it even better.

Friday, August 28, 2009

Making T4MVC comply with CLS

FXCop rule CA1014 tells you to mark your assembly as CLSCompliant. If you adhere to this, your T4MVC (as of build 2.4.01 at least) will throw compiler warnings saying stuff like

Identifier

‘xxxController._Actions’
’xxxController._Views’
’T4MVC._Dummy’

is not CLS-Compliant.

If you have 10 controllers and 50 views this will result in 61 warnings…

The reason is that these are public members that start with an underscore, which is a CLS no-no:

http://stackoverflow.com/questions/1195030/why-is-this-name-not-cls-compliant

 

To solve this, edit the T4MVC.tt file to mark the code with a [CLSCompliant(false)] attribute.  Once you start this, you’ll also find additional warnings from mebers that implement the now-explicitly-non-compliant members, but a few more [CLSCompliant(false)] attribute handles that. Full code in gist below.

<#
/*
T4MVC Version 2.4.01 + AIS Modifications
AIS Modifications are to fix StyleCop and CLSCompliant-related issues:
* Marking the file with an // <auto-generated /> comment
* Changing the region to include the term 'Generated Code'
* Marking all public members that start with _ as CLSCompliant(false)
* Marking all members that reference said members as CLSCompliant(false)
Full details at http://mo.notono.us/2009/08/making-t4mvc-comply-with-stylecop.html
and http://mo.notono.us/2009/08/making-t4mvc-comply-with-cls.html
Find latest version on http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=24471&ProjectName=aspnet
Written by David Ebbo, with much feedback from the MVC community (thanks all!)
david.ebbo@microsoft.com
http://twitter.com/davidebbo
http://blogs.msdn.com/davidebb
Related blog posts:
http://blogs.msdn.com/davidebb/archive/2009/07/28/t4mvc-2-4-updates-settings-file-sub-view-folders-actionname-support-and-more.aspx
http://blogs.msdn.com/davidebb/archive/2009/06/30/t4mvc-2-2-update-routing-forms-di-container-fixes.aspx
http://blogs.msdn.com/davidebb/archive/2009/06/26/the-mvc-t4-template-is-now-up-on-codeplex-and-it-does-change-your-code-a-bit.aspx
http://blogs.msdn.com/davidebb/archive/2009/06/17/a-new-and-improved-asp-net-mvc-t4-template.aspx
Feel free to use and modify to fit your needs.
This T4 template for ASP.NET MVC apps creates strongly typed helpers that eliminate the use
of literal strings when referring the controllers, actions and views.
To use it, simply copy it and T4MVC.settings.t4 to the root of your MVC application.
This will enable the following scenarios:
Refer to controller, action and view names as shown in these examples:
- MVC.Dinners.Name: "Dinners" (controller name).
- MVC.Dinners.Views.DinnerForm: "DinnerForm" (view name)
- MVC.Dinners.Actions.Delete: "Delete" (action name)
Strong type certain scenarios that refer to controller actions. e.g.
- Html.ActionLink("Delete Dinner", MVC.Dinners.Delete(Model.DinnerID))
- Url.Action(MVC.Dinners.Delete(Model.DinnerID))
- RedirectToAction(MVC.Dinners.Delete(dinner.DinnerID))
- Route defaults e.g.
routes.MapRoute(
"UpcomingDinners",
"Dinners/Page/{page}",
MVC.Dinners.Index(null)
);
Refer to your static images and script files with strong typing, e.g.
Instead of <img src="/Content/nerd.jpg" ...>, you can write <img src="<%= Links.Content.nerd_jpg %>" ...>
Instead of <script src="/Scripts/Map.js" ...>, you can write <script src="<%= Links.Scripts.Map_js %>" ...>
Or if the file name is dynamic, you can write: Links.Content.Url("foo.jpg")
KNOWN ISSUES:
- Users running VisualSVN have reported some errors when T4MVC tries to change actions to virtual and controllers to partial.
The suggestion when that happens is to manually make those changes. This is just a one time thing you need to do.
- It will not locate controllers that live in a different project or assembly
HISTORY:
2.4.01 (07-29-2009):
- Put all the generated code in a T4MVC #region. This is useful to tell tools like ReSharper to ignore it.
- Fixed issue where controller methods returning generic types cause template to blow up
- Added a setting in T4MVC.settings.t4 to turn off the behavior that always keeps the template dirty
2.4.00 (07-28-2009):
- Added support for configurable settings in a separate T4MVC.settings.t4 file
- Added a parameter-less pseudo-action for every action that doesn't already have a parameter-less overload
- Added support for having T4MVC.tt in a sub folder instead of always at the root of the project
- Fixed issue when a base controller doesn't have a default ctor
- Added T4Extensions into System.Web.Mvc namespace to fix ambiguous resolution issue
- Misc cleanup
2.3.01 (07-10-2009):
- Fixed issue with [ActionName] attribute set to non literal string values (e.g. [ActionName(SomeConst + "Abc")])
- Fixed duplication issue when partial controller classes have a base type which contains action methods
- Skip App_LocalResources when processing views
- Cleaned up rendering logic
2.3.00 (07-07-2009):
- Added support for sub view folders
- Added support for [ActionName] attribute
- Improved handling when the controller comes from a different project
- Don't try to process generic controller classes
2.2.03 (07-06-2009):
- Added support for action methods defined on controller base classes
- Improved error handling when not able to change actions to virtual and controllers to partial
2.2.02 (07-01-2009):
- Fixed break caused by incorrect support for derived ActionResult types in 2.2.01
- Fixed issue with duplicate view tokens getting generated when you have both foo.aspx and foo.ascx
2.2.01 (07-01-2009):
- Added support for action methods that return a type derived from ActionResult (as opposed to exactly an ActionResult)
- Fixed issue when controller is using partial classes
- Fixed folder handling logic to deal with generated files
- Fixed issue with folder names that are C# keyword
- Throw NotSupportedException instead of NotImplementedException to avoid being viewed as a TODO
2.2.00 (06-30-2009):
- Added strongly typed support to MapRoute
- Changed constructor generation to avoid confusing IoC containers
- Fixed issue with empty Content folder
- Fixed issue with abstract controller base classes
2.1.00 (06-29-2009):
- Added Html.BeginForm overloads that use the strongly typed pattern
- Added Url() helpers on static resources to increase flexibility
- Changed generated constants (view and action names, static files) to be readonly strings
- Fixed null ref exception in Solution Folder logic
2.0.04 (06-28-2009):
- Fixed issue with files and folders with names starting with a digit
2.0.03 (06-27-2009):
- Rework code element enumeration logic to work around a VS2010 issue. The template should now work with VS2010 beta 1!
- Reduced some redundancy in the generated code
2.0.02 (06-27-2009):
- Added ActionLink overloads that take object instead of dictionary (from both Html and Ajax)
2.0.01 (06-26-2009):
- Fixed issue with files and folders with invalid identifier characters (e.g. spaces, '-', '.')
2.0.00 (06-26-2009): as described in http://blogs.msdn.com/davidebb/archive/2009/06/26/the-mvc-t4-template-is-now-up-on-codeplex-and-it-does-change-your-code-a-bit.aspx
- Added support for refactoring in Action methods
- The T4 file automatically runs whenever you build, instead of being done manually
- Support for strongly typed links to static resources
- Fix: supports controllers that are in sub-folders of the Controllers folder and not directly in there
- Fix: works better with nested solution folder
- Random other small fixes
1.0.xx (06-17-2009): the original based on this post
http://blogs.msdn.com/davidebb/archive/2009/06/17/a-new-and-improved-asp-net-mvc-t4-template.aspx
*/
#>
<#@ template language="C#v3.5" debug="true" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<# PrepareDataToRender(this); #>
// <auto-generated />
// This file was generated by a T4 template.
// Don't change it directly as your change would get overwritten. Instead, make changes
// to the .tt file (i.e. the T4 template) and save it to regenerate this file.
#region T4MVC Generated Code
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using <#= T4MVCNamespace #>;
[CompilerGenerated]
public static class <#= HelpersPrefix #> {
<# foreach (var controller in GetControllers()) { #>
public static <#= controller.FullClassName #> <#= controller.Name #> = new <#= controller.DerivedClassName #>();
<# } #>
}
<# foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #>
namespace <#= controller.Namespace #> {
public partial class <#= controller.ClassName #> {
protected <#= controller.ClassName #>() { }
}
}
<# } #>
<# foreach (var controller in GetControllers()) { #>
namespace <#= controller.Namespace #> {
public <# if (!controller.SharedViewFolder) { #>partial <# } #>class <#= controller.ClassName #> {
<# if (!controller.SharedViewFolder) { #>
<# if (!controller.HasExplicitConstructor) { #>
public <#= controller.ClassName #>() { }
<# } #>
[CLSCompliant(false)]
[CompilerGenerated]
protected <#= controller.ClassName #>(_Dummy d) { }
protected RedirectToRouteResult RedirectToAction(ActionResult result) {
var callInfo = (IT4MVCActionResult)result;
return RedirectToRoute(callInfo.RouteValues);
}
<# foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #>
[NonAction]
public ActionResult <#= method.Name #>() {
return new T4MVC_ActionResult(Name, Actions.<#= method.ActionName #>);
}
<# } #>
[CompilerGenerated]
public readonly string Name = "<#= controller.Name #>";
static readonly _Actions s_actions = new _Actions();
[CLSCompliant(false)]
[CompilerGenerated]
public _Actions Actions { get { return s_actions; } }
[CLSCompliant(false)]
[CompilerGenerated]
public class _Actions {
<# foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
public readonly string <#= method.ActionName #> = <#= method.ActionNameValueExpression #>;
<# } #>
}
<# } #>
static readonly _Views s_views = new _Views();
[CLSCompliant(false)]
[CompilerGenerated]
public _Views Views { get { return s_views; } }
[CLSCompliant(false)]
[CompilerGenerated]
public class _Views {
<# RenderControllerViews(controller);#>
}
}
}
<# } #>
namespace <#= T4MVCNamespace #> {
<# foreach (var controller in GetControllers().Where(c => !c.SharedViewFolder)) { #>
[CompilerGenerated]
public class <#= controller.DerivedClassName #>: <#= controller.FullClassName #> {
public <#= controller.DerivedClassName #>() : base(_Dummy.Instance) { }
<# foreach (var method in controller.ActionMethods) { #>
public override <#= method.ReturnType #> <#= method.Name #>(<# method.WriteFormalParameters(true); #>) {
var callInfo = new T4MVC_<#= method.ReturnType #>("<#= controller.Name #>", Actions.<#= method.ActionName #>);
<# if (method.Parameters.Count > 0) { #>
<# foreach (var p in method.Parameters) { #>
callInfo.RouteValues.Add("<#= p.Name #>", <#= p.Name #>);
<# } #>
<# }#>
return callInfo;
}
<# } #>
}
<# } #>
[CLSCompliant(false)]
[CompilerGenerated]
public class _Dummy {
private _Dummy() { }
public static _Dummy Instance = new _Dummy();
}
}
namespace System.Web.Mvc {
[CompilerGenerated]
public static class T4Extensions {
public static string ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result) {
return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary());
}
public static string ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes) {
return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes));
}
public static string ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary<string, object> htmlAttributes) {
return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary(), htmlAttributes);
}
public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod) {
return htmlHelper.BeginForm(result, formMethod, null);
}
public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod, object htmlAttributes) {
return BeginForm(htmlHelper, result, formMethod, new RouteValueDictionary(htmlAttributes));
}
public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod, IDictionary<string, object> htmlAttributes) {
var callInfo = (IT4MVCActionResult)result;
return htmlHelper.BeginForm(callInfo.Action, callInfo.Controller, callInfo.RouteValues, formMethod, htmlAttributes);
}
public static string Action(this UrlHelper urlHelper, ActionResult result) {
return urlHelper.RouteUrl(result.GetRouteValueDictionary());
}
public static string ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions) {
return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions);
}
public static string ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions, object htmlAttributes) {
return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions, new RouteValueDictionary(htmlAttributes));
}
public static string ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes) {
return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions, htmlAttributes);
}
public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result) {
return routes.MapRoute(name, url, result, (ActionResult)null);
}
public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults) {
// Start by adding the default values from the anonymous object (if any)
var routeValues = new RouteValueDictionary(defaults);
// Then add the Controller/Action names and the parameters from the call
foreach (var pair in result.GetRouteValueDictionary()) {
routeValues.Add(pair.Key, pair.Value);
}
// Create and add the route
var route = new Route(url, routeValues, new MvcRouteHandler());
routes.Add(name, route);
return route;
}
public static RouteValueDictionary GetRouteValueDictionary(this ActionResult result) {
return ((IT4MVCActionResult)result).RouteValues;
}
public static void InitMVCT4Result(this IT4MVCActionResult result, string controller, string action) {
result.Controller = controller;
result.Action = action; ;
result.RouteValues = new RouteValueDictionary();
result.RouteValues.Add("Controller", controller);
result.RouteValues.Add("Action", action);
}
}
}
[CompilerGenerated]
public interface IT4MVCActionResult {
string Action { get; set; }
string Controller { get; set; }
RouteValueDictionary RouteValues { get; set; }
}
<# foreach (var resultType in ResultTypes.Values) { #>
[CompilerGenerated]
public class T4MVC_<#= resultType.Name #> : <#= resultType.Name #>, IT4MVCActionResult {
public T4MVC_<#= resultType.Name #>(string controller, string action): base(<# resultType.Constructor.WriteNonEmptyParameterValues(true); #>) {
this.InitMVCT4Result(controller, action);
}
<# foreach (var method in resultType.AbstractMethods) { #>
<#= method.IsPublic ? "public" : "protected" #> override void <#= method.Name #>(<# method.WriteFormalParameters(true); #>) { }
<# } #>
public string Controller { get; set; }
public string Action { get; set; }
public RouteValueDictionary RouteValues { get; set; }
}
<# } #>
namespace Links {
<#
foreach (string folder in StaticFilesFolders) {
ProcessStaticFiles(Project, folder);
}
#>
}
#endregion T4MVC
<#@ Include File="T4MVC.settings.t4" #>
<#+
const string T4MVCNamespace = "T4MVC";
const string ControllerSuffix = "Controller";
static DTE Dte;
static Project Project;
static HashSet<ControllerInfo> Controllers;
static Dictionary<string, ResultTypeInfo> ResultTypes;
static TextTransformation TT;
static string T4FileName;
static IEnumerable<ControllerInfo> GetControllers() {
return Controllers.Where(c => !c.IsAbstract);
}
static IEnumerable<ControllerInfo> GetAbstractControllers() {
return Controllers.Where(c => c.IsAbstract);
}
void PrepareDataToRender(TextTransformation tt) {
TT = tt;
T4FileName = Path.GetFileName(Host.TemplateFile);
Controllers = new HashSet<ControllerInfo>();
ResultTypes = new Dictionary<string, ResultTypeInfo>();
// Get the DTE service from the host
Dte = (DTE)((IServiceProvider)Host).GetService(typeof(SDTE));
Project = GetProjectContainingT4File(Dte);
if (Project == null) {
Error("Could not find the VS Project containing the T4 file.");
return;
}
ProcessControllersFolder(Project);
ProcessAllViews(Project);
}
Project GetProjectContainingT4File(DTE dte) {
// Just locating the Project that contains this T4 file is immensely difficult.
// If there is an easier way to do it, I'd love to know!
foreach (Project project in dte.Solution.Projects) {
Project foundProject = GetProjectContainingT4File(project);
if (foundProject != null)
return foundProject;
}
return null;
}
Project GetProjectContainingT4File(Project project) {
if (project.ConfigurationManager != null) {
// It's a Project
// Get the folder the project is in, making sure it ends with '\'
string projectFolder = Path.GetDirectoryName(project.FileName);
if (!projectFolder.EndsWith("\\"))
projectFolder += '\\';
// If the .tt file is not under this folder, skip the project
if (!Host.TemplateFile.StartsWith(projectFolder, StringComparison.OrdinalIgnoreCase))
return null;
// Get the relative path to the .tt file inside the project
string t4SubPath = Host.TemplateFile.Substring(projectFolder.Length);
// If it has the T4 file we're looking for, it's the one we want
ProjectItem projectItem = GetProjectItem(project, t4SubPath);
if (projectItem != null) {
// If the .tt file is not opened, open it
if (projectItem.Document == null)
projectItem.Open(Constants.vsViewKindCode);
if (AlwaysKeepTemplateDirty) {
// Mark the .tt file as unsaved. This way it will be saved and update itself next time the
// project is built. Basically, it keeps marking itself as unsaved to make the next build work.
// Note: this is certainly hacky, but is the best I could come up with so far.
projectItem.Document.Saved = false;
}
return project;
}
}
else if (project.ProjectItems != null) {
// It may be a solution folder. Need to recurse.
foreach (ProjectItem item in project.ProjectItems) {
if (item.SubProject == null)
continue;
Project foundProject = GetProjectContainingT4File(item.SubProject);
if (foundProject != null)
return foundProject;
}
}
return null;
}
void ProcessControllersFolder(Project project) {
// Get the Controllers folder
ProjectItem controllerProjectItem = GetProjectItem(project, ControllersFolder);
if (controllerProjectItem == null)
return;
ProcessControllersRecursive(controllerProjectItem);
}
void ProcessControllersRecursive(ProjectItem projectItem) {
// Recurse into all the sub-items (both files and folder can have some - e.g. .tt files)
foreach (ProjectItem item in projectItem.ProjectItems) {
ProcessControllersRecursive(item);
}
if (projectItem.FileCodeModel != null) {
// Process all the elements that are namespaces
foreach (CodeNamespace ns in GetNamespaces(projectItem.FileCodeModel.CodeElements)) {
ProcessControllerTypesInNamespace(ns);
}
}
}
void ProcessControllerTypesInNamespace(CodeNamespace ns) {
foreach (CodeClass2 type in GetClasses(ns.Members)) {
// Only process types that end with Controller
// REVIEW: this check is not super reliable. Should look at base class.
if (!type.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase))
continue;
// Don't process generic classes (their concrete derived classes will be processed)
if (type.IsGeneric)
continue;
// Make sure the class is partial
if (type.ClassKind != vsCMClassKind.vsCMClassKindPartialClass) {
try {
type.ClassKind = vsCMClassKind.vsCMClassKindPartialClass;
}
catch {
// If we couldn't make it partial, give a warning and skip it
Warning(String.Format("{0} was not able to make the class {1} partial. Please change it manually if possible", T4FileName, type.Name));
continue;
}
Warning(String.Format("{0} changed the class {1} to be partial", T4FileName, type.Name));
}
// Collect misc info about the controller class and add it to the collection
var controllerInfo = new ControllerInfo() {
Namespace = ns.Name,
ClassName = type.Name
};
// Either process new ControllerInfo or integrate results into existing object for partially defined controllers
var target = Controllers.Add(controllerInfo) ? controllerInfo : Controllers.First(c => c.Equals(controllerInfo));
target.HasExplicitConstructor |= HasExplicitConstructor(type);
target.HasExplicitDefaultConstructor |= HasExplicitDefaultConstructor(type);
if (type.IsAbstract) {
// If it's abstract, set a flag and don't process action methods (derived classes will)
target.IsAbstract = true;
}
else {
// Process all the action methods in the controller
ProcessControllerActionMethods(target, type);
}
}
}
void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current) {
// We want to process not just the controller class itself, but also its parents, as they
// may themselves define actions
for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1)) {
// If the type doesn't come from this project, some actions on it will fail. Try to get a real project type if possible.
if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject) {
// Go through all the projects in the solution
foreach (Project prj in Dte.Solution.Projects) {
// Skip it if it's the current project or doesn't have a code model
if (prj == Project || prj.CodeModel == null)
continue;
// If we can get a local project type, use it instead of the original
var codeType = prj.CodeModel.CodeTypeFromFullName(type.FullName);
if (codeType != null && codeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject) {
type = (CodeClass2)codeType;
break;
}
}
}
foreach (CodeFunction2 method in GetMethods(type)) {
// Ignore non-public methods
if (method.Access != vsCMAccess.vsCMAccessPublic)
continue;
// This takes care of avoiding generic types which cause method.Type.CodeType to blow up
if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType)
continue;
// We only support action methods that return an ActionResult derived type
if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult")) {
Warning(String.Format("{0} doesn't support {1}.{2} because it doesn't return a supported ActionResult type", T4FileName, type.Name, method.Name));
continue;
}
// If we haven't yet seen this return type, keep track of it
if (!ResultTypes.ContainsKey(method.Type.CodeType.Name)) {
var resTypeInfo = new ResultTypeInfo(method.Type.CodeType);
ResultTypes[method.Type.CodeType.Name] = resTypeInfo;
}
// Make sure the method is virtual
if (!method.CanOverride) {
try {
method.CanOverride = true;
}
catch {
// If we couldn't make it virtual, give a warning and skip it
Warning(String.Format("{0} was not able to make the action method {1}.{2} virtual. Please change it manually if possible", T4FileName, type.Name, method.Name));
continue;
}
Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name));
}
// Collect misc info about the action method and add it to the collection
controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, controllerInfo));
}
}
}
void ProcessAllViews(Project project) {
// Get the Views folder
ProjectItem viewsProjectItem = GetProjectItem(project, ViewsRootFolder);
if (viewsProjectItem == null)
return;
// Go through all the sub-folders in the Views folder
foreach (ProjectItem item in viewsProjectItem.ProjectItems) {
// We only care about sub-folders, not files
if (!IsFolder(item))
continue;
ControllerInfo controller;
// Treat Shared as a pseudo-controller for consistency
if (item.Name.Equals("Shared", StringComparison.OrdinalIgnoreCase)) {
controller = new ControllerInfo() { SharedViewFolder = true, Namespace = T4MVCNamespace, ClassName = "Shared" + ControllerSuffix };
Controllers.Add(controller);
}
else {
// Find the controller for this view folder
controller = Controllers.SingleOrDefault(c => c.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase));
if (controller == null) {
Error(String.Format("The Views folder has a sub-folder named '{0}', but there is no matching controller", item.Name));
continue;
}
}
AddViewsRecursive(String.Empty, item.ProjectItems, controller.ViewsFolder);
}
}
void AddViewsRecursive(string prefix, ProjectItems items, ViewsFolderInfo viewsFolder) {
// Go through all the files in the subfolder to get the view names
foreach (ProjectItem item in items) {
string viewName = Path.GetFileNameWithoutExtension(item.Name);
if (item.Kind == Constants.vsProjectItemKindPhysicalFile) {
if (Path.GetExtension(item.Name).Equals(".master", StringComparison.OrdinalIgnoreCase))
continue; // ignore master files
viewsFolder.Views.Add(viewName);
}
else if (item.Kind == Constants.vsProjectItemKindPhysicalFolder) {
var subViewFolder = new ViewsFolderInfo() { FullName = prefix + viewName };
if (subViewFolder.Name.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase))
continue;
viewsFolder.SubFolders.Add(subViewFolder);
AddViewsRecursive(prefix + viewName + "/", item.ProjectItems, subViewFolder);
}
}
}
void RenderControllerViews(ControllerInfo controller) {
PushIndent(" ");
RenderViewsRecursive(controller.ViewsFolder);
PopIndent();
}
void RenderViewsRecursive(ViewsFolderInfo viewsFolder) {
// For each view, generate a readonly string
foreach (string view in viewsFolder.Views) {
WriteLine("public readonly string " + Sanitize(view) + " = \"" + viewsFolder.GetFullViewName(view) + "\";");
}
// For each sub folder, generate a class and recurse
foreach (var subFolder in viewsFolder.SubFolders) {
string newClassName = Sanitize(subFolder.Name);#>
static readonly _<#=newClassName#> s_<#=newClassName#> = new _<#=newClassName#>();
public _<#=newClassName#> <#=newClassName#> { get { return s_<#=newClassName#>; } }
public partial class _<#=newClassName#>{
<#+
PushIndent(" ");
RenderViewsRecursive(subFolder);
PopIndent();
WriteLine("}");
}
}
void ProcessStaticFiles(Project project, string folder) {
ProjectItem folderProjectItem = GetProjectItem(project, folder);
if (folderProjectItem != null) {
ProcessStaticFilesRecursive(folderProjectItem, "~");
}
}
void ProcessStaticFilesRecursive(ProjectItem projectItem, string path) {
if (IsFolder(projectItem)) { #>
[CompilerGenerated]
public static class @<#=Sanitize(projectItem.Name) #> {
public static string Url() { return VirtualPathUtility.ToAbsolute("<#=path#>/<#=projectItem.Name#>"); }
public static string Url(string fileName) { return VirtualPathUtility.ToAbsolute("<#=path#>/<#=projectItem.Name#>/" + fileName); }
<#+
PushIndent(" ");
// Recurse into all the items in the folder
foreach (ProjectItem item in projectItem.ProjectItems) {
ProcessStaticFilesRecursive(item, path + "/" + projectItem.Name);
}
PopIndent();
#>
}
<#+
}
else { #>
public static readonly string <#=Sanitize(projectItem.Name)#> = Url("<#=projectItem.Name#>");
<#+
// Non folder items may also have children (virtual folders, Class.cs -> Class.Designer.cs, template output)
// Just register them on the same path as their parent item
foreach (ProjectItem item in projectItem.ProjectItems) {
ProcessStaticFilesRecursive(item, path);
}
}
}
ProjectItem GetProjectItem(Project project, string name) {
return GetProjectItem(project.ProjectItems, name);
}
ProjectItem GetProjectItem(ProjectItems items, string subPath) {
ProjectItem current = null;
foreach (string name in subPath.Split('\\')) {
try {
// ProjectItems.Item() throws when it doesn't exist, so catch the exception
// to return null instead.
current = items.Item(name);
}
catch {
// If any chunk couldn't be found, fail
return null;
}
items = current.ProjectItems;
}
return current;
}
// Return all the CodeNamespaces in the CodeElements collection
static IEnumerable<CodeNamespace> GetNamespaces(CodeElements codeElements) {
return GetElements<CodeNamespace>(codeElements);
}
// Return all the CodeClass2 in the CodeElements collection
static IEnumerable<CodeClass2> GetClasses(CodeElements codeElements) {
return GetElements<CodeClass2>(codeElements);
}
// Return all the CodeFunction2 in the CodeElements collection
static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass) {
// Only look at regular method (e.g. ignore things like contructors)
return GetElements<CodeFunction2>(codeClass.Members)
.Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
}
// Check if the class has any explicit constructor
static bool HasExplicitConstructor(CodeClass2 codeClass) {
return GetElements<CodeFunction2>(codeClass.Members).Any(
f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor);
}
// Check if the class has a default (i.e. no params) constructor
static bool HasExplicitDefaultConstructor(CodeClass2 codeClass) {
return GetElements<CodeFunction2>(codeClass.Members).Any(
f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor && f.Parameters.Count == 0);
}
// Find a method with a given name
static CodeFunction2 GetMethod(CodeClass2 codeClass, string name) {
return GetMethods(codeClass).FirstOrDefault(f => f.Name == name);
}
static IEnumerable<T> GetElements<T>(CodeElements codeElements) where T : class {
// Note: this code can be simplified to just:
// return codeElements.OfType<T>();
// But this breaks on VS2010 beta due to a VS bug (that should be fixed in VS2010 beta 2).
// For now, work around using this alternate code which avoids getting the enumerator
for (int i = 1; i <= codeElements.Count; i++) {
var codeNamespace = codeElements.Item(i) as T;
if (codeNamespace != null)
yield return codeNamespace;
}
}
// Return whether a ProjectItem is a folder and not a file
static bool IsFolder(ProjectItem item) {
return (item.Kind == Constants.vsProjectItemKindPhysicalFolder);
}
static string Sanitize(string token) {
// Replace all invalid chars by underscores
token = Regex.Replace(token, @"[\W\b]", "_", RegexOptions.IgnoreCase);
// If it starts with a digit, prefix it with an underscore
token = Regex.Replace(token, @"^\d", @"_$0");
// Check for reserved words
// TODO: Clean this up and add other reserved words (keywords, etc)
if (token == "Url") token = "_Url";
return token;
}
// Data structure to collect data about a controller class
class ControllerInfo {
public ControllerInfo() {
ActionMethods = new HashSet<ActionMethodInfo>();
ViewsFolder = new ViewsFolderInfo();
}
// True when this is not a real controller, but a placeholder for the Shared views folder
public bool SharedViewFolder { get; set; }
public bool HasExplicitConstructor { get; set; }
public bool HasExplicitDefaultConstructor { get; set; }
public bool HasDefaultConstructor { get { return !HasExplicitConstructor || HasExplicitDefaultConstructor; } }
public bool IsAbstract { get; set; }
public string ClassName { get; set; }
public string Name {
get {
// Trim the Controller suffix
return ClassName.Substring(0, ClassName.Length - ControllerSuffix.Length);
}
}
public string Namespace { get; set; }
public string FullClassName {
get {
return Namespace + "." + ClassName;
}
}
public string DerivedClassName {
get {
if (SharedViewFolder)
return FullClassName;
return "T4MVC_" + ClassName;
}
}
public HashSet<ActionMethodInfo> ActionMethods { get; set; }
IEnumerable<ActionMethodInfo> ActionMethodsWithNoParameters {
get {
return ActionMethods.Where(m => m.Parameters.Count == 0);
}
}
public IEnumerable<ActionMethodInfo> ActionMethodsUniqueWithoutParameterlessOverload {
get {
return ActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
}
}
// Return a list of actions without duplicate names (even with multiple overloads)
public IEnumerable<ActionMethodInfo> ActionMethodsWithUniqueNames {
get {
return ActionMethods.Distinct(new ActionComparer());
}
}
class ActionComparer : IEqualityComparer<ActionMethodInfo> {
public bool Equals(ActionMethodInfo x, ActionMethodInfo y) {
return x.ActionName == y.ActionName;
}
public int GetHashCode(ActionMethodInfo obj) {
return obj.ActionName.GetHashCode();
}
}
public ViewsFolderInfo ViewsFolder { get; private set; }
public override string ToString() {
return Name;
}
public override bool Equals(object obj) {
return obj != null && FullClassName == ((ControllerInfo)obj).FullClassName;
}
public override int GetHashCode() {
return FullClassName.GetHashCode();
}
}
// Info about a view folder, its views and its sub view folders
class ViewsFolderInfo {
public ViewsFolderInfo() {
Views = new HashSet<string>();
SubFolders = new List<ViewsFolderInfo>();
}
public string FullName { get; set; }
public string Name {
get {
return FullName.Substring(FullName.LastIndexOf("/") + 1);
}
}
public HashSet<string> Views { get; private set; }
public List<ViewsFolderInfo> SubFolders { get; set; }
public string GetFullViewName(string viewName) {
if (String.IsNullOrEmpty(FullName))
return viewName;
return FullName + "/" + viewName;
}
}
// Data structure to collect data about a method
class FunctionInfo {
protected CodeFunction2 _method;
private string _signature;
public FunctionInfo(CodeFunction2 method) {
_method = method;
// Build a unique signature for the method, used to avoid duplication
_signature = method.Name;
// Process all the parameters
Parameters = new List<MethodParamInfo>();
foreach (var p in GetElements<CodeParameter2>(method.Parameters)) {
Parameters.Add(
new MethodParamInfo() {
Name = p.Name,
Type = p.Type.AsString
});
_signature += "," + p.Type.AsString;
}
}
public string Name { get { return _method.Name; } }
public string ReturnType { get { return _method.Type.CodeType.Name; } }
public bool IsPublic { get { return _method.Access == vsCMAccess.vsCMAccessPublic; } }
public List<MethodParamInfo> Parameters { get; private set; }
// Write out all the parameters as part of a method declaration
public void WriteFormalParameters(bool first) {
foreach (var p in Parameters) {
if (first)
first = false;
else
TT.Write(", ");
TT.Write(p.Type + " " + p.Name);
}
}
// Pass non-empty param values to make sure the ActionResult ctors don't complain
// REVIEW: this is a bit dirty
public void WriteNonEmptyParameterValues(bool first) {
foreach (var p in Parameters) {
if (first)
first = false;
else
TT.Write(", ");
switch (p.Type) {
case "string":
TT.Write("\" \"");
break;
case "byte[]":
TT.Write("new byte[0]");
break;
default:
TT.Write("null");
break;
}
}
}
public override bool Equals(object obj) {
return obj != null && _signature == ((FunctionInfo)obj)._signature;
}
public override int GetHashCode() {
return _signature.GetHashCode();
}
}
// Data structure to collect data about an action method
class ActionMethodInfo: FunctionInfo {
public ActionMethodInfo(CodeFunction2 method, ControllerInfo controller): base(method) {
// Normally, the action name is the method name. But if there is an [ActionName] on
// the method, get the expression from that instead
ActionNameValueExpression = '"' + Name + '"';
for (int i = 1; i <= method.Attributes.Count; i++) {
var attrib = (CodeAttribute2)method.Attributes.Item(i);
if (attrib.FullName == "System.Web.Mvc.ActionNameAttribute") {
var arg = (CodeAttributeArgument)attrib.Arguments.Item(1);
ActionNameValueExpression = arg.Value;
}
}
}
public string ActionName { get { return Name; } }
public string ActionNameValueExpression { get; set; }
}
// Data about an ActionResult derived type
class ResultTypeInfo {
CodeType _codeType;
public ResultTypeInfo(CodeType codeType) {
_codeType = codeType;
var ctor = GetElements<CodeFunction2>(_codeType.Members).FirstOrDefault(
f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor);
Constructor = new FunctionInfo(ctor);
}
public string Name { get { return _codeType.Name; } }
public FunctionInfo Constructor { get; set; }
public IEnumerable<FunctionInfo> AbstractMethods {
get {
return GetElements<CodeFunction2>(_codeType.Members).Where(
f => f.MustImplement).Select(f => new FunctionInfo(f));
}
}
}
class MethodParamInfo {
public string Name { get; set; }
public string Type { get; set; }
}
#>
view raw T4MVC.tt hosted with ❤ by GitHub

Labels: , , , , , ,

Monday, May 24, 2010

Comparative Performance of Underscore.js in Chrome and IE

I came across the very handy-looking Undersore.js today, and clicked on the test & benchmark link. I first ran the test in Chrome.  The results below show number of operations per second.  Looks like each, map, keys, values, and range are pretty inexpensive operations, whereas uniq and intersect should be used sparingly.  All makes sense. 

Then out of curiosity, I ran the same tests in IE and Firefox.  The exact numbers are not significant as the results vary by 10-20% between subsequent runs in the same browser, but the range is pretty illustrative.  And yes, I know IE9 is harder, better, faster, stronger, so this is not a fair fight.  I can’t wait for IE9 to replace every previous IE version…

  Ops/sec

(higher is

better)
Test Chrome 6 IE 8 Firefox 3.6

_.each()

20213

510

3249

_(list).each()

13570

493

3161

jQuery.each()

3637

209

910

_.map()

18581

303

5488

jQuery.map()

7084

686

8519

_.pluck()

10852

282

4785

_.uniq()

127

1

33

_.uniq() (sorted)

308

210

84

_.sortBy()

1641

45

359

_.isEqual()

4962

869

1826

_.keys()

22675

1142

4295

_.values()

24551

321

5435

_.intersect()

83

1

20

_.range()

33345

1223

5262

Again, why I use Chrome as my default browser.

Labels: , , , , ,