LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

ASP.NET translation tools & gotchas

clock March 8, 2013 11:35 by author JKealey

We’ve recently translated one of our applications and thought we’d share the tools & techniques we used. In particular, every time we perform some ASP.NET translation, we hit a few gotchas. We kept facing the same problems every time we worked on translation, so we figured we might as well write everything down in a post for everyone’s benefit.

Technique

Because we’re translating an ASP.NET WebForms application, the main process is to open an *.aspx or *.ascx, switch to Design view, and perform Tools –> Generate Local Resource. This generates a *.resx file and adds the relevant markup in your source file. Tools are available to perform the actual translation and create the *.resx files in other languages.

The core issue here is that you need to perform this operation for each individual file. Potentially thousands of times and/or until you go crazy. (Personally, I find it frustrating that bulk resource generation is not a core VS.NET feature. )

 

Step 1 - Bulk Generate Local Resource

Instead of wasting our time opening each individual file, we found a macro on this forum. The macro does not run in VS.NET 2012, so we loaded up our old VS.NET 2010 and ran it from there.

The macro wasn’t flawless – it sometimes randomly crashed after processing files for half an hour. Deleting Visual Studio’s *.suo file and restarting it seemed to help.

 

Step 2 - Realize that VS.NET corrupted your files

I assume one of the reasons bulk resource generation is not a core VS.NET feature is because the feature is (in addition to being slow) partially broken.

Gotcha: Inline scripts/comments are sometimes deleted.

At a high level, any script blocks in your *.aspx/*.ascx files are vulnerable to deletion. Generate Local Resource will simply strip them out if they are contained in an <asp:UpdatePanel …/>.   We filed a bug on Microsoft Connect which was not deemed important enough to be fixed.

This is appalling because it will introduce pernicious bugs in your application that only show up at runtime, if you don’t pay close attention to each and every individual file.

For example:

<script>alert('<%="Some Constant" %>');</script>
<script>alert('<%= btnSomething.ClientID %>');</script>
<%-- <asp:Button runat="server" id="btn" Text="Some button that I may need to re-enable later"/> --%>
<% 
if (Request.QueryString["test"]=="bye") 
    Response.Write("Goodbye World"); 
else 
    Response.Write("Hello World"); 
%>

Would become the following, after Generate Local Resource, because everything

<script>alert('');</script>
<script>alert('');</script>

 

Admittedly, some of the inline code above is bad practice.  However, the silent deletion causes needle-in-a-haystack type bugs at runtime.

We decided to remove all of our inline code blocks from our code to avoid having issues during local resource generation.

Gotcha: culture=”auto” and uiculture=”auto” is added to all *.aspx files

These values, added in the *.aspx header, force the page to change culture based on the browser’s settings. In our application, this was not desirable as it by-passed the logic defined in our Global.asax file. (Our users can change their language via the web applications itself, not via their web browser settings.)

For more information, see this post by Rick Strahl.

Gotcha: Nested controls can be problematic

When trying to localize a LinkButton containing an Image and literal, the Image will be dropped.

<asp:LinkButton ID="lnkHello" runat="server" OnClick="lnkHello_Click">
    <asp:Image ID="imgEdit" runat="server" ImageUrl="~/images/icons/edit.gif"></asp:Image>
    HELLO!
</asp:LinkButton>


Becomes:

<asp:LinkButton ID="lnkHello" runat="server" OnClick="lnkHello_Click" meta:resourcekey="abcdef">    
    HELLO!
</asp:LinkButton>

 

To solve this issue, the nested controls must be separated.

Gotcha: Ajax:Accordion breaks during Generate Local Resource

If you are using <ajax:Accordion ../> from the ASP.NET Ajax control toolkit, be aware that it will be corrupted after generating *.resx files. The fix is simple: delete the erroneously added Accordion Extender.

 

Step 3 – Extract other hardcoded strings.

Your *.aspx/*.ascx files and your *.cs files may contain additional strings which must be extracted. Back in 2008, we had create a Macro to help with this process but in this iteration, we simply used JetBrains ReSharper. The VS.NET plugin made it easy to find strings which had not been extracted, and push them into *.resx files.  ReSharper is jam-packed with other useful features, but we’ve found that it does have a significant impact on performance in our solution.

 

Step 4 – Perform the actual translation

Back in 2008, we released a web application to help translate RESX files. We’re no longer using this application – there are better options out there. We picked Zeta Resource Editor and it worked nicely.

Conclusion

The tools available today are much better than they were five years ago, but one piece of the puzzle (Generate Local Resource) is still far from perfect. We’d love to see an improved version (in either VS.NET or ReSharper) which would:

  • Not delete inline code or comments inside UpdatePanels
  • Would be configurable (insert culture=”auto” everywhere? etc.)
  • Would produce reviewable reports of any changes which are not additions of meta:resourcekey to controls. (Performing a diff with regular tools is very time consuming given the thousands of changes.)
  • Could be executed in batch in a timely manner across a project

PS: Big thanks to @plgelinas for his research efforts for this project.



jQuery plugin to postback an ASP.NET button

clock August 20, 2012 10:52 by author EtienneT

We use jQuery a lot here at LavaBlast, but we also use ASP.NET webforms. We needed a simple reusable way to cause a postback on an asp.net managed Button or LinkButton.

Here is how it would be used for <asp:Button ID=”btShow” runat=”server” OnClick=”DoSomething” />

// Cause btShow to postback to the server
$('[id$="btShow"]').postback();

If you are not too familiar with jQuery, the selector [id$=”btShow”] search for any control with an id which ends with “btShow”.

Since ASP.NET 4.0, you could also use the new ClientIDMode=”Static” property on the server control to be able to have a static ID on the client and use a jQuery selector like this: $(‘#btShow’), but this is the matter of another discussion completely.

The postback() method is a jQuery plugin which I include here:

(function ($)
{
    $.fn.extend({
        postback: function ()
        {
            return this.each(function ()
            {
                if (this && "undefined" != typeof this.click)
                    this.click();
                else if (this && this.tagName.toLowerCase() == "a" && this.href.indexOf('javascript:') == 0)
                    eval(this.href.toString().replace('javascript:', ''));
            });
        }
    });    
})(jQuery);

Feel free to use this and let us know if you find any problems with the code.



Style ASP.NET Web Forms Validators with qTip 2

clock August 13, 2012 08:20 by author EtienneT

View demo | Download source

The default validators inside ASP.NET Web Forms are quite uninteresting and require some styling work to look adequate.  Recently, we’ve been using the qTip2 jQuery library and we love it.  qTip enables you to add visually pleasant tooltips to any element.  For example, you simply add a “title” attribute to any element and then apply qTip to this element and the “title” attribute will be used as the tooltip’s text.  This is the simplest use case.  Here’s an example; with our FranchiseBlast registration form.

image

When you try to submit this form and the validation doesn’t pass, we replaced the default ASP.NET validators with styled qTip tooltips beside each validated element.

SNAGHTML6770ee44

Like you can see, the validators have absolute positioning, which enables them to flow outside of the bounds of the registration panel.  We could also easily change the position of the bubble in relation to the validated element and also change the bubble tip position.

Let’s take a look at what was needed to accomplish this, using a simple ASP.NET project. Here is the main ASP.NET code for the ASPX page.  Nothing fancy: a simple form with some validators:

Default.aspx

<asp:ScriptManager ID="p" runat="server">
    <Scripts>
        <asp:ScriptReference Path="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" />
        <asp:ScriptReference Path="~/Scripts/qtip/jquery.qtip.min.js" />
        <asp:ScriptReference Path="~/Scripts/validators.js" />
    </Scripts>
</asp:ScriptManager>
<fieldset class="Validate" style="width: 300px">
    <legend>Tell us about yourself</legend>
    <div>
        <span class="label">Business Name:</span>
        <asp:TextBox ID="txtBusinessName" runat="server" />
        <asp:RequiredFieldValidator ID="rfvBusinessName" runat="server" ControlToValidate="txtBusinessName" Text="Your business name is required" SetFocusOnError="true" EnableClientScript="true" />
    </div>
    <div class="alternate">
        <span class="label">Your Name:</span>
        <asp:TextBox ID="txtYourName" runat="server" />
        <asp:RequiredFieldValidator ID="rfvName" runat="server" ControlToValidate="txtYourName" Text="Your name is required" SetFocusOnError="true" EnableClientScript="true" />
    </div>
    <div>
        <span class="label">Your Email:</span>
        <asp:TextBox runat="server" ID="txtEmail" />
        <asp:RequiredFieldValidator ID="rfvEmail" runat="server" ControlToValidate="txtEmail" Text="Email is required" SetFocusOnError="true" EnableClientScript="true" />
        <asp:RegularExpressionValidator runat="server" ID="revEmail" Text="Invalid Email" ControlToValidate="txtEmail" SetFocusOnError="true" ValidationExpression="^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\w]*[0-9a-zA-Z])*\.)+[a-zA-Z]{2,9})$" EnableClientScript="true" />
    </div>
</fieldset>
<asp:Button runat="server" ID="btnCreateAccount" CssClass="Next" Text="Create Account" />

Starting from the top, we need jQuery and also qTip to be added to our page.  The interesting JavaScript code in located in ~/Scripts/validators.js.  The rest of the code here is a simple ASP.NET form.  One important thing is that each element to be validated is enclosed in a <div> with his corresponding validators.  This is important because we will use this convention later in our script to find the associated validators for an input control.

I also have to mention that I added some lines in the .skin file of the App_Theme:

Default.skin

<asp:RequiredFieldValidator runat="server" CssClass="ErrorMsg" Display="Dynamic" />
<asp:CustomValidator runat="server" CssClass="ErrorMsg" Display="Dynamic" />
<asp:RangeValidator runat="server" CssClass="ErrorMsg" Display="Dynamic" />
<asp:CompareValidator runat="server" CssClass="ErrorMsg" Display="Dynamic" />
<asp:RegularExpressionValidator runat="server" CssClass="ErrorMsg" Display="Dynamic" />

This will force CssClass=”ErrorMsg” on validators.  This will be used next in our JavaScript code to find the validators:

validator.js

Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function () {
    function getValidator() {
        return $(this).parent().find('.ErrorMsg').filter(function () { return $(this).css('display') != 'none'; });
    }
 
    var inputs = '.Validate input:text, .Validate select, .Validate input:password';
 
    var submit = $('input:submit');
 
    var q = $(inputs).qtip({
        position: {
            my: 'center left',
            at: 'center right'
        },
        content: {
            text: function (api) {
                return getValidator.call(this).html();
            }
        },
        show: {
            ready: true,
            event: 'none'
        },
        hide: {
            event: 'none'
        },
        style: {
            classes: 'ui-tooltip-red ui-tooltip-shadow ui-tooltip-rounded'
        },
        events: {
            show: function (event, api) {
                var $this = api.elements.target;
                var validator = getValidator.call($this);
                if (validator.length == 0)
                    event.preventDefault();
            }
        }
    });
 
    if (window.Page_ClientValidate != undefined) {
        function afterValidate() {
            $(inputs).each(function () {
                var validator = getValidator.call(this);
 
                if (validator.length > 0) {
                    var text = validator.html();
 
                    $(this).addClass('Error').qtip('show').qtip('option', 'content.text', text);
//                    validator.hide();
 
                }
                else
                    $(this).removeClass('Error').qtip('hide');
            });
        }
 
        $(inputs).blur(afterValidate);
 
        var oldValidate = Page_ClientValidate;
 
        Page_ClientValidate = function (group) {
            oldValidate(group);
 
            afterValidate.call(this);
 
            submit.removeAttr('disabled');
        }
    }
});

There is much to explain in this code.  First we register a new function to be executed each time there’s an ASP.NET PostBack on the page here: Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function () { … });

The function getValidator finds the visible ASP.NET validators associated to a control to be validated.  We use the fact that the control to validate and the validators are contained inside a <div>.

We apply qTip to the inputs to validate and we get the text of the message by finding the visible validators.  Also we have some logic to prevent showing the qTip element if there aren’t any visible validators.

We also do some monkey patching at the end where we inject our own code inside the Page_ClientValidate ASP.NET JavaScript method.  To do that, we simply get a reference to the Page_ClientValidate function, create a new function with our additional code (calling the old Page_ClientValidate) plus we override window.Page_ClientValidate with our new function.  This new function have both the new and old functionality.

You would probably have to modify this code a little bit to fit your needs, but this shows how you could integrate qTip2 for nicer validators in ASP.NET Web Forms.

View demo | Download source



Slash your ASP.NET compile/load time without any hard work

clock December 1, 2010 09:18 by author JKealey

Jason has a cool eye :) FranchiseBlast, our franchise management platform, is a ‘large’ solution inside Visual Studio which currently contains 35 projects. In total, we manage over 60 interrelated projects, and have always been concerned at improving the compilation/load performance on our development machines. This post is a quick summary of what we did to keep things as fast as possible.

Personally, when it starts taking over a minute to compile or load my application, I start throwing things. This was the case early this week and with a bit of work, I managed to cut down things by an order of magnitude (from around 140 seconds down to around 14 seconds) with only software changes.  This is what prompted me to write this post.

There are three things worth improving:

  1. Compilation time
  2. First load time (ASP.NET)
  3. Application speed / database performance

 

I only want to talk about the first two; the only tip I’ll give you for #3 is to get your hands on a profiler such as dotTrace as it is a real time saver. 

Get better hardware (Big impact)

You’ll get the best bang for your buck by upgrading your hard disk, especially if you’re using a single 5400 RPM or 7200 RPM drive (download benchmark software to evaluate your current disk). Our projects are stored on a solid state disk. We currently use Intel X25-M G2, but the RevoDrive x2 looks much faster (basically a RAID-0 array of SSDs) if you have an available PCI-Express x4 slot. If you’re cheap and find the SSDs to be too small, just get two large 7200 RPM drives and put them in RAID-0. Make sure you’ve got a robust backup solution.

We have 12GB RAM on our development machines and a recent Core i7 processor, but nothing fancy. This is more than sufficient.

Store your temporary IIS files on your fastest disk or a RAM disk

Depending on the amount of RAM you have, it may make sense to use a RAM disk. I use my RAM disk for my temporary internet files and for IIS’s temporary folder (compilation results). I haven’t measured specific performance details but since I have so much free RAM, might as well try use it in creative ways.

To speed up the first load time, you can tell IIS to store its temporary files on your RAM disk (or fastest disk) by changing the following setting in your web.config files:

<compilation ... tempDirectory="q:\temp\iistemp\"> ... </compilation>

 

You can either change your project files directly, or, if you’ve lazy and have numerous applications running on your development machine (like I do), update the system-wide web.config files. Note that you need to update this for each runtime version of the Framework and, if running a 64-bit machine, for both Framework and Framework64. On my machine, I needed to modify the following files:

   1:  C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\Web.config
   2:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.config
   3:  C:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG\Web.config
   4:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\Web.config

 

Trade-off: If you save your RAM disk when shutting down, you’ll notice how much longer it takes to reboot. I can live with that, rebooting only once every couple months.

Review a few magical settings (Most impact)

When an ASP.NET website is loaded for the first time, it pre-compiles all your pages and user controls. Once done, everything runs faster. This is great for production websites, but horrible for your development machine. Why?  When programming, you’re usually only modifying a page or two (or back-end code). You’ll iteratively make a change, compile, launch the website, test, and start over; often dozens of times. A two minute compile/load time (like we had) forces you to lose focus and get distracted. The following setting makes pre-compilation more selective, making the first load time massively faster in development scenarios. On my machine, it cut the first load time from around 74 seconds to 6 seconds.

<compilation ... batch="false"> ...</compilation>

 

While on the subject of random boolean flags that make your life better, I should mention the following:

<compilation ... optimizeCompilations="true"> ... </compilation>

This flag has a number of gotcha’s that are documented here. I’ve enabled it for now, although my quick tests didn’t show any significant performance improvements compared to the previous one. However, I was probably not testing the right thing.

Restructure your projects

Try multiple solutions

About a year ago, we took 50% of our least used projects and moved them into a secondary solution. We compile that solution only when changing one of these projects, once every couple months. After rebuilding the secondary solution, we copy the *.dll files to a separate folder. Our primary projects reference those pre-generated libraries. I should repeat this process again, but I like having all my projects in one place and splitting them was a pain. However, this is probably the only sustainable way to manage extremely large solutions.

Selectively build the necessary projects

Using Visual Studio’s Configuration Manager, I also created different active solution configurations in my primary solution with only a subset of the projects included in the build.  I can thus swap between configurations depending on my primary task (working on our point of sale instead of FranchiseBlast, for example). This can drastically reduce compilation time, but I don’t use it as often as I should because:

  1. I find the user interface slow to refresh (Visual Studio).
  2. My dependency tree is complex and going through the list of projects to remove those I don’t want built is a pain.
  3. I context-switch a lot and often hit cases where I modify a project and forget that is not included in the build.

 

A simpler option is to build only the current project (and its dependencies) instead of all projects. Just make sure you don’t accidentally break the build (recompile everything before a commit).

Parallel compilation (Big impact)

Following Scott Hanselman’s post, I setup my project to compile in parallel. I did add a few options to make it easier for me to see errors/warnings without MSBuild’s typical clutter. (I also had a post-build action using NAnt which I set to quiet). This reduced my full rebuild time from 96 seconds down to 16 (after a clean solution). In a typical recompilation, we’re talking roughly 8 seconds instead of around 66.

Title: Parallel Build
Command: C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
Arguments: /m $(SolutionFileName) /v:m /ds /nologo /clp:Summary;Verbosity=minimal
Initial Directory: $(SolutionDir)
Check: Use Output window.

Trade-off: Error messages aren’t nicely presented in the Visual Studio Error List tab and you are not notified when the build is complete. 

Other noteworthy attempts

Over time, I’ve tried a few things that didn’t work out. In general, they improved things slightly but I don’t use them on a daily basis due to the tradeoffs.

I tried putting the whole project (source and output folders) onto the RAM disk but its volatility scared me away, regardless of the performance enhancements (Off the top of my head, it was between 25% and 50%). I then tried putting my only project’s bin folders on my RAM disk (by creating symbolic links from my bin folders to my RAM disk). This also had a positive impact on performance, but not significant enough to warrant the kludge (around 25% reduction in compilation time).

I also found a few tips & tricks for larger projects on Stack Overflow. First, I tried putting ‘Copy Local’ to false for all project references. This gave me a 25% reduction in compilation time, but broke my deployment scripts which needed all the files in the bin folder.  Separately, I configured all my projects Output Paths to the same folder, avoiding content duplication on the disk. This also gave me a 25% reduction in compilation time. Oddly enough, moving this folder to my RAM disk did not impact performance.

Summary

I hope this post gave you some ideas on how to improve your compilation speeds and first load times. I didn’t intend to give exact benchmarks, as performance will vary greatly depending on your projects. However, the main lesson learned is that there are dozens of improvements you can make; it’s up to you to try them out and keep what works for you.

The top three time savers for us are:

  1. Setting batch=false in the ASP.NET configuration page. (Now ~10 times faster)
  2. Parallel compilation for our projects (Now ~8 times faster)
  3. Better hardware (Now between 2 and 5 times faster)

 

Honorable mention goes to splitting your solution files, even though it’s painful and time consuming process to setup.

Do you have any other tips & tricks you’d like to share with us?



Gotcha: ASP.NET and exceptions in asynchronous tasks

clock October 30, 2008 13:00 by author JKealey

Awesomeness Here's a little piece of information you might not know about how ASP.NET 2.0 and above operate. Simply put, if an unhandled exception occurs in a secondary thread which has been launched by your ASP.NET application, IIS will force your application to restart. This can mean lost sessions, bad server side state, slow reloads and, in general, is a bad thing. However, these kinds of exceptions are a pain to discover and resolve if you don't know what you're looking for because the built-in ASP.NET error handler does not get fired and your user is not redirected to an error page, unless something breaks after the web server restarts. (This article explains the cryptic messages you will see in the Event Log).

Here's a simple scenario based on the integration between our interactive kiosk created for The Code Factory co-working space and Twitter. We want our web application to post messages on Twitter when certain events occur (member check-in and member check out). Because we did not want to re-invent the wheel, we found a Twitter C# Library with a permissive software license. Noticing that posting to Twitter slowed down our application and because we didn't want our application to depend on Twitter's availability, we decided to run this code asynchronously. Being careless, we supposed that should anything bad occur in this second thread (timeouts, invalid login/password, etc.), the system will simply not be able to post, which was not a problem for us. We were wrong, and the whole web application restarted because of this. (This was not the case in ASP.NET 1.x and was previously discussed by others).

In any case, here's some sample code to run the Twitter library asynchronously, including a try/catch.

using System;
using System.Collections.Generic;
using System.Text;
 
namespace LavaBlast.Util.Twitter
{
    public class AsyncTwitter
    {
        public void Update(string userName, string password, string status)
        {
            TwitterDelegate caller = new TwitterDelegate(UpdateTwitter);
            caller.BeginInvoke(userName, password, status, new AsyncCallback(CallbackMethod), caller);
        }
 
        protected delegate void TwitterDelegate(string userName, string password, string status);
        protected static void CallbackMethod(IAsyncResult ar)
        {
            TwitterDelegate caller = (TwitterDelegate)ar.AsyncState;
            caller.EndInvoke(ar);
        }
        protected static void UpdateTwitter(string userName, string password, string status)
        {
            try
            {
                Twitter t = new Twitter();
                t.Update(userName, password, status, Twitter.OutputFormatType.XML);
            }
            catch (Exception ex)
            {
                LavaBlast.ElectronicJournal.Error("Unable to post to twitter.", ex);
                // ignore. 
            }
        }
    }
}

The lesson learned is unhandled exceptions in other threads can wreak havoc on your ASP.NET application. To help debug these kinds of errors when they occur, I do suggest you setup the HttpModule developed by Peter A. Bromberg that adds the actual exception in your Event Log. Peter describes the problem in more detail than I do, and is worth a read.

Have a Happy Halloween! (Halloween will be weird for us in 2008 because we were hit by the year's first snowstorm on Tuesday.)

halloween

kick it on DotNetKicks.com


Upgrading to SubSonic v2.1

clock July 10, 2008 16:05 by author JKealey

The timing for the release of SubSonic v2.1 could not have been better as we're between time-critical projects at the moment. As our readers know, we've used SubSonic as our business object code generator since we first launched the company. I spent a few hours this morning doing the migration of our codebase and it seems to have gone smoothly. We've posted some cool improvements we've made to SubSonic in previous posts: Improved ManyManyList Control, Object Change Tracking, and an Improved ObjectDataSource Controller. Migrating to v2.1 involved a few changes and this post will describe them briefly. As this is currently a work in progress, we'll let the dust settle before writing a more formal post.

LavaBlastManyManyList :)

Rob integrated the LavaBlastManyManyList control into SubSonic. It does strike me as uncommon for an open source project to list the contributor in the class name, but who am I to complain? :)

Changes to our SubSonicHelper and SubSonicController.

SubSonic changed the base classes for their objects. Therefore, we have to change our own SubSonicController<T, C> to extend RecordBase<T> instead of AbstractRecord<T>. In our SubSonicHelper, we changed AbstractRecord<T> and ActiveRecord<T> to RecordBase<T> but, for some reason, we also had an ActiveList<T> which we changed to AbstractList<T> to match the rest of the application.

SubSonic Collections no longer extend List<T>

Collections are now extending BindingList<T>, apparently for improved DataBinding support. However, this breaks all the code you may have which uses the fact that Collections were generic lists: Sort, Find, FindAll, FindLast, AddRange, Exists, etc. Luckily for us, we have replacement methods for Sort/Find, which are easier to use but not as powerful as custom delegates/predicates. Rewriting the 70-odd locations in our code to avoid using methods from the List<T> interface isn't what I consider fun and you may feel the same way. The code we had to rewrite was non-trivial and rewriting all these locations without being able to recompile and test (as we don't have unit tests that specifically check that the items in a Collection are sorted the right way, for example), we took the decision to go with a low-impact change.

We edited CS_ClassTemplate.aspx and CS_ViewTemplate.aspx and added the following method to both collections:

   1: public List<<%=className%>> ToList()  {
   2:     return new List<<%=className%>>(Items); // shallow copy
   3: }

BindingList<T> has a protected property named Items which is indeed a List<T>. We didn't check the implementation details, but since it doesn't make this property public, we can assume that playing with that list directly (removing items from the list for example) might screw up the original collection. Therefore, we're creating a shallow copy of the List and using that in our code when necessary. Now that everything compiles and works properly, we can rewrite code where performance is more important (and use the original SubSonic collection instead).

Found two bugs, one old, one new.

We've reported two bugs in the SubSonic's brand new issue tracker on Google Code. (Issue 3 is a rare case relating to composite keys and paging, it probably won't affect you as it has been around forever. However, Issue 4 is a bit more worrisome as it implies that most of your code that uses StoredProcedures might not work anymore without a small workaround until they release SubSonic v2.1.1.)

Conclusion

I hope this helps all of you who were trying to get our SubSonic v2.0.3 code working on SubSonic v2.1! When everything will have been tested thoroughly, we'll post more source code.

kick it on DotNetKicks.com


Skinned Login Control

clock April 14, 2008 14:16 by author EtienneT
Here is our login form in FranchiseBlast.  We think it's a pretty cool login form and it was not that hard to do.  It only requires basic CSS and some jQuery.
 
 

How we did it

The only things you need is an image like this one here:

inputLogin

Then we used the following CSS to define our text boxes style.  The "Login" css class is applied to the ASP.NET Login control and the class "TextBox" are applied to both textboxes in the login control.

.Login .Textbox, .Login .Hover
{
    width: 337px;
    height: 17px;
    background:transparent url(images/inputlogin.png) no-repeat top left;
    color: Black;
    border: none;
    padding: 5px;
    font-weight: bold;
}
  
.Login .Hover
{
    background:transparent url(images/inputlogin.png) no-repeat bottom left;
}

 

Has you can see, the only difference for .Hover class is that we tell the background to show the bottom of the picture (the orange part) instead of the top of the image.  If Internet Explorer supported the "focus" CSS pseudo class then it would be much simpler, but IE doesn't support it, so we have to use jQuery to achieve the effect.

Don't forget to add jQuery.js somewhere in the page and then you can add the following script to your page:

$('.Login .Textbox').focus(function(){
  $(this).attr('class', 'Hover');
});
  
$('.Login .Textbox').blur(function(){
  $(this).attr('class', 'Textbox');
});

 

Basically the code above registers an event to all DOM elements which have the "Textbox" CSS class and are children of a control of the "Login" CSS class. The first call registers an event handler on the focus event of the text box which changes the class to Hover.  We do the exact opposite for the blur event when we the text box loses it's focus.   There may be a better way to do this why jQuery; if you know how, let us know.

Finally, as a special added touch, we use an AnimationExtender after a successful login:

<ajax:AnimationExtender ID="animLogin" runat="server" TargetControlID="LoginButton">
<Animations>
    <OnClick>
        <Sequence>
            <FadeOut Duration=".5" Fps="20" AnimationTarget="pnlLogin" />
        </Sequence>
    </OnClick>
</Animations>
</ajax:AnimationExtender>

 

One last thing, if you use this AnimationExtender, you have to make sure your validators don't run on the client side. Validation must occur on the server otherwise the fade out animation will still occur and the login control will disappear. For example, we used a RequiredFieldValidator for both the username and password text boxes and we had to set the EnableClientScript property to false on both these validators.

This concludes how to do a skinned Login control à la LavaBlast.

kick it on DotNetKicks.com


Scripting an ASP.NET installation in Win2k3

clock March 27, 2008 08:26 by author JKealey

A month ago, I posted an article on a few console commands for managing ASP.NET applications and IIS. In the weeks that followed, I was contacted by my old friends at iWeb Technologies to help them automate their ASP.NET setup. I spent a couple hours creating the required scripts and I thought I'd post my real world example here!

By the way, iWeb is an exceptional web hoster (1TB of space, unmetered traffic, unlimited domains, $3 a month, and excellent technical support; what more could a web developer want?). I've been with them for nine years already!

Scripting the creation of a new website and configuring it for ASP.NET

Task: Given a domain name (lavablast.com) and a network/local path (\\fileserver\hosting\www.lavablast.com\web\ or c:\inetpub\wwwroot\www.lavablast.com\web\), setup IIS so that ASP.NET works on the root of the domain.

  1. Create an application pool
  2. Create a web site and associate it to the application pool
  3. Ensure both that the www subdomain works (www.lavablast.com and lavablast.com should load this website)
  4. Enable ASP.NET v2.0 on this website.
  5. Give ASP.NET read/write access to the folder.
  6. Add default.aspx to the default documents.

 

I ended up using the ADSUTIL.VBS script for most of these tasks. I used the iisweb command to create the web application, but it doesn't support network paths. I create it using a temp local path and end up using adsutil to change it to a network path. 

My googling skills are what made this task so short. Here are some of my references:

Finally, the IISBatchUtils collection of scripts provided the most help. Here's why. 

I have created batch files to make adding new websites to the server very quick and easy. The only tricky part about setting up new websites from batch file (or the command line) is that Microsoft's ADSUTL utility does not correctly add host headers like it says it does- rather than appending the new headers, it blindly sticks them in and possibly covers existing host headers that might already be set up.

I used both the original adsutil, Josh's modified adsutil, and some of his code to extract the site id from the IIS Metabase by using the site name.

The script

See the actual script for the variable definitions.

:Create
REM Step 1 - Create Application Pool
CSCRIPT //nologo %IWEB_ADSUTIL% CREATE w3svc/AppPools/%IWEB_DOMAIN% IIsApplicationPool
 
REM Step 2 - Create WebServer
iisweb /create %TEMP% %IWEB_DOMAIN% /d %IWEB_DOMAIN% /ap %IWEB_DOMAIN%
 
REM Step 3- Find new SiteID for further scripting. 
cscript //nologo iisbatchutils/translate.js "%IWEB_DOMAIN%" > siteid.txt
for /f %%I in (siteid.txt) do SET IWEB_SITEID=%%I
 
REM Step 4 - Add www. to site URL - uses their own custom adsutil because of bug in the normal one. 
cscript %IWEB_ADSUTIL2% append w3svc/%IWEB_SITEID%/serverbindings ":80:www.%IWEB_DOMAIN%"
 
REM Step 5 - set various website permissions. 
cscript //nologo %IWEB_ADSUTIL% set w3svc/%IWEB_SITEID%/accessread "true"
cscript //nologo %IWEB_ADSUTIL% set w3svc/%IWEB_SITEID%/accesswrite "true"
cscript //nologo %IWEB_ADSUTIL% set w3svc/%IWEB_SITEID%/root/AppFriendlyName %IWEB_DOMAIN%
cscript //nologo %IWEB_ADSUTIL% set w3svc/%IWEB_SITEID%/root/path %IWEB_Path%
cscript //nologo %IWEB_ADSUTIL% set w3svc/%IWEB_SITEID%/root/DefaultDoc Default.htm,Default.asp,index.htm,Default.aspx,index.asp,index.html
 
REM Step 6 - Cleanup
del siteid.txt
 
GOTO End

 

What about setting up IIS to use ASP.NET v2.0?

I did find a link showing me how to change the ASP.NET version from v1.1 to v2.0 using regiis, but later discovered that this stopped & restarted all websites... something catastrophic to do in a production environment. All the hosted websites (not only the new one) would lose their session state, for example. Fortunately, I found that you can change the root website and any new websites created afterwards will inherit the default ASP.NET version. Run the following once:

@echo off
echo WARNING: THIS WILL CHANGE THE DEFAULT ASP.NET VERSION FOR ALL NEW SITES TO V2.0
pause
 
REM will propagate to new sites. 
%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis -sn W3SVC/
 
REM does not propagate
REM cscript %IWEB_ADSUTIL% set w3svc/accessread "true"
REM cscript %IWEB_ADSUTIL% set w3svc/accesswrite "true"

 

Scripting the deletion of a website

Deletion is much simpler, as you can see below.

 

:Delete
iisweb /delete %IWEB_DOMAIN%
CSCRIPT //nologo %IWEB_ADSUTIL% DELETE w3svc/AppPools/%IWEB_DOMAIN%
 
GOTO End

Conclusion

I had a fun time perfecting my scripting skills while creating a concrete example that solves someone's problem. I discovered that the devil is in the details, but that numerous people have worked on similar problems in the past. I ended up creating another script which runs this one numerous times, according to actions found in a local text file. Simply put, iWeb's PHP system appends operations to a file (create this site, delete that one, create this other site) and mine performs the operation and empties the task list. That way, the script can be run periodically and all the PHP coders need to do is append to a particular file.  

Download the code.

kick it on DotNetKicks.com



How I lost my WinDbg virginity

clock March 13, 2008 10:32 by author JKealey

image Jeff Moser's recent post on becoming a grandmaster developer was very inspiring. I'm now consciously forcing myself to work on different, harder, problems on a daily basis. Today, I wanted to tackle a nasty memory leak on FranchiseBlast that had been bugging us for months. Actually, it only made the ASP.NET application restart twice (which has no impact because our session state is kept in the state service), but we've been postponing this bug for months :)

The bug was simple to reproduce. Simply refresh a particular page a hundred or so times and observe the worker process' memory usage continuously increase. dotTrace was not very useful today, because most of the memory was consumed by Strings.

I landed on the If broken it is, fix it you should blog. Reading the blog post and noticing how Tess knows much more than I do about debugging .NET applications, I figured I'd improve my .NET debugging skills by playing with WinDbg. My guess is that many of you haven't played with WinDbg either and that's why I thought I'd write this post. With no previous experience with the tool, I managed to learn enough to be able to solve my bug within a couple hours (learning time included). However, this is a situation where I am grateful to have had the chance to play with the low-level details (assembly language / computer architecture) during my undergrad software engineering studies.

WinDbg Installation

The article says simply run "!dumpheap -min 85000 -stat" to figure out which objects are using over 85k memory. Vaguely remembering doing something similar in VS.NET, I tried the command window, to no avail. After some googling, I understood we wanted to launch WinDbg with the SOS extension (Son of Strike.. not Summer of Sam :)). I managed to get everything running (installed WinDbg, attached to w3wp.exe, ran ".loadby sos mscorwks") and later found setup instructions on the same blog, which would have made my life easier. I still haven't remembered (nor have I looked) how to load SOS inside VS.NET instead of the external tool, but I'm pretty sure it can be done.

Playing around

I played around with various strategies found on the same blog, such as figuring out how much I was caching.  I didn't think caching was my problem, but wanted to play around with the tool.

Attempting to run an interesting command found on the aforementioned blog post, I ran into a syntax error:

.foreach (runtime {!dumpheap -mt 0x01b29dfc -short}){.echo ******************;!dumpobj poi(${runtime}+0x3c);.echo cache size

Of course, this was a blog formatting issue, as the real command was:

.foreach (runtime {!dumpheap -mt 0x01b29dfc -short}){.echo ******************;!dumpobj poi(${runtime}+0x3c);.echo cache size:;!objsize poi(${runtime}+0xc) }

It didn't teach me much :) One command that appeared to be very interesting to me was !dumpaspnetcache. This appeared to be exactly what I was looking for. However, the command is only available in SOS for .NET 1.0/1.1 and I'm trying to debug a .NET 2.0 application. I did lose some time unloading the current SOS and loading the one in the clr10 subfolder (which offers !dumpaspnetcache), but it complained that the command didn't work for .NET 2.0.

The main lesson learned here is that !dumpaspnetcache does not work on .NET 2.0. There is a workaround listed in the comments of the blog post, but I didn't try it out.

Solving a problem

  1. I identified that most of my memory was consumed by strings using !dumpheap -stat.
  2. I ran !dumpheap -min 85000 -stat to a view only the largest strings. I observed only three strings (out of approximately 400,000) and that, in total, they accounted for less than 10% of the memory used by my strings.
  3. I ran !dumpheap -mt 790fd8c4 -min 85000 to view the memory locations of these strings (where the MT 790fd8c4 was in the results of step 2).
  4. I ran !dumpobj 0c6800c0 to display the contents of one of the strings (where the address 0c6800c0 was in the result of step 3.)
  5. I saw the string was a simple ASP.NET AJAX script built by the framework. Repeating for the other objects, I didn't find anything interest. In addition, these were only 3 of my 400,000 strings.
  6. I ran !dumpheap -min 10000 -max 85000 -stat to get a feeling of how many objects were of a particular size. I repeated the exercise for various ranges and ended up discovering that 90% of my strings were under 100 bytes (50% of the memory usage).
  7. I ran !dumpobj 17386ce8 on one of the random 100 byte strings and discovered a country name. Interesting. This means I have a country name in here that is not getting disposed. I only have country names in my address editor, which uses a SubSonic DropDownList.
  8. I ran !gcroot 17386ce8 to look at who was referencing this string and confirmed that it wasn't getting collected by the garbage collector (see below).
  9. I looked at the handles and noticed that I had a user control which was listed as an event listener. Therefore, I discovered that we were registering the UserControl to listen to an event but never broke the link when we were done with the UserControl.
  10. Looking at the code, I saw we were adding the event listener in the Page_Load but never removing it. I now simply remove the event handler during the Page_PreRender event, when listening to events it is no longer necessary in my scenario.
  11. I repeatedly loaded the page and saw memory go up but back down again after the garbage collector does its job. Problem solved! (knock on wood)

Here's the result of my !gcroot command.

DOMAIN(0E68E998):HANDLE(Pinned):ed712f0:Root:0a646bd0(System.Object[])->
17cd3dc4(System.EventHandler`1[[LBBoLib.BO.Utility.GenericEventArgs`1[[System.Web.UI.Page, System.Web]], LBBoLib]])->
161a6630(System.Object[])->
173c8e18(System.EventHandler`1[[LBBoLib.BO.Utility.GenericEventArgs`1[[System.Web.UI.Page, System.Web]], LBBoLib]])->
1736abe4(ASP.usercontrols_content_searchpanel_ascx)->
1626d460(ASP.users_aspx)->
1736e234(ASP.usercontrols_content_users_usereditor_ascx)->
1736ff4c(ASP.usercontrols_shared_user_accountinformation_ascx)->
1737d558(ASP.usercontrols_shared_user_addressselectionpanel_ascx)->
1737d974(ASP.usercontrols_shared_user_addresseditor_ascx)->
1737e044(SubSonic.Controls.DropDown2)->
173866dc(System.Web.UI.WebControls.ListItemCollection)->
173866ec(System.Collections.ArrayList)->
173894b8(System.Object[])->
17386d0c(System.Web.UI.WebControls.ListItem)->
17386ce8(System.String)

Conclusion

In total, I only spent a couple hours fiddling around with WinDbg but I am very happy to have a new tool in my tool belt (and to have solved my bug). I strongly recommend reading the various tutorials on Tess's blog. I also found a WinDbg / SOS cheat sheet, which you might find interesting.

kick it on DotNetKicks.com



Common console commands for the typical ASP.NET developer

clock February 25, 2008 14:19 by author JKealey

iis As an ASP.NET web developer, there are a few tasks that I must perform often for which I am glad to be able to perform via the command line. GUIs are great, but there are some things that are simply faster to do via the command line. Although we do have Cygwin installed to enhance our tool belt with commands like grep, there are a few ASP.NET related commands that I wanted to share with you today. Some of these are more useful on Windows 2003 server (because you can run multiple worker processes), but I hope you will find them useful.

1) Restarting IIS

The iisreset command can be used to restart IIS easily from the command line. Self-explanatory.

Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted

2) Listing all ASP.NET worker processes

You can use tasklist to get the running worker processes.

tasklist /FI "IMAGENAME eq w3wp.exe"

Image Name PID Session Name Session# Mem Usage
========================================================================
w3wp.exe 129504 Console 0 40,728 K

You can also use the following command if you have Cygwin installed (easier to remember)

 

tasklist | grep w3wp.exe

 

w3wp.exe 4456 Console 0 54,004 K
w3wp.exe 5144 Console 0 101,736 K
w3wp.exe 2912 Console 0 108,684 K
w3wp.exe 3212 Console 0 136,060 K
w3wp.exe 852 Console 0 133,616 K
w3wp.exe 352 Console 0 6,228 K
w3wp.exe 1556 Console 0 155,264 K
w3wp.exe 3480 Console 0 6,272 K

3) Associating a process ID with a particular application pool

Should you want to monitor memory usage for a particular worker process, the results shown above are not very useful. Use the iisapp command.

W3WP.exe PID: 4456 AppPoolId: .NET 1.1
W3WP.exe PID: 5144 AppPoolId: CustomerA
W3WP.exe PID: 2912 AppPoolId: CustomerB
W3WP.exe PID: 3212 AppPoolId: Blog
W3WP.exe PID: 852 AppPoolId: LavaBlast
W3WP.exe PID: 352 AppPoolId: CustomerC
W3WP.exe PID: 1556 AppPoolId: CustomerD
W3WP.exe PID: 3480 AppPoolId: DefaultAppPool

By using iisapp in conjunction with tasklist, you can know which task is your target for taskkill.

4) Creating a virtual directory

When new developers checkout your code for the first time (or when you upgrade your machine), you don’t want to spend hours configuring IIS. You could back up the metabase and restore it later on, but we simply use iisvdir. Assuming your root IIS has good default configuration settings for your project, you can create a virtual directory like so:

iisvdir /create “Default Web Site” franchiseblast c:\work\lavablast\franchiseblast\

 

5) Finding which folder contains the desired log files.

IIS saves its log files in %WINDOWS%\System32\LogFiles, but it creates a different subdirectory for each web application. Use iisweb /query to figure out which folder to go check out.

Connecting to server ...Done.
Site Name (Metabase Path) Status IP Port Host
==============================================================================

Default Web Site (W3SVC/1) STARTED ALL 80 N/A
port85 (W3SVC/858114812) STARTED ALL 85 N/A

6) Many more commands…

Take a peek at the following articles for more command-line tools that might be useful in your context:

http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/b8721f32-696b-4439-9140-7061933afa4b.mspx?mfr=true

http://www.tech-faq.com/using-iis-command-line-utilities-to-manage-iis.shtml

Conclusion

There are numerous command line tools distributed by Microsoft that help you manage your ASP.NET website. Obviously, the commands listed here are the tip of the iceberg! Although many developers know about these commands because they had to memorize them for some test, many are not even aware of their existence. Personally, I feel that if you write a single script that sets up IIS as you need it to develop, you’ll save time setting up new developers or when you re-install your operating system. Script it once and reap the rewards down the road.

kick it on DotNetKicks.com  



Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in