When a request occurs for an ASP.NET page, the response is processed through a series of events before being sent to the client browser. These events, known as the ASP.NET Page Life Cycle, are a complicated headache when used improperly, manifesting as odd exceptions, incorrect data, performance issues, and general confusion. It seems simple when reading yet-another-book-on-ASP.NET, but never when applied in the real world. What is covered in a few short pages in many ASP.NET books (and sometimes even just a few short paragraphs), is much more complicated outside of a "Hello, World!" application and inside of the complex demands of the enterprise applications that developers create and maintain in their day-to-day work life. As close to the core as the life cycle is to any ASP.NET web application, the complications and catches behind this system never seems to get wide coverage on study guides or other documentation. But, they should. A little help on the Page Life Cycle is never a bad thing. In this series, I will go over the events that make up the ASP.NET Page Life Cycle, as well as some tips and tricks on how to get the most out of this event structure while avoiding the traps and pitfalls. Rather than pursuing broad coverage of the entire ASP.NET Framework, we'll dive deeply into the "small" portion that is the ASP.NET Page Life Cycle. Events of the ASP.NET Page Life Cycle I want to start at the beginning. The primary make-up of the Page Life Cycle is the events that process any ASP.NET requests. Unlike the public static void main of a WinForms application, where everything based on methods, the execution of a page request is the execution of these events. These events, which execute in a particular order, handle the entire request, including loading all of the controls, processing all of the form data, handling all user-initiated actions, and rendering the page to the web browser. Knowing the order in which these events are executed, as well as the responsibility of each event in processing your request, is important for developing solid, quality ASP.NET applications. Start This is where the page object is instantiated, and where the initial properties of the page are set. Page properties such as Response and Request, UICulture (similar to the UICulture property within a WinForms thread), and the value of IsPostBack are all determined and assigned. No controls are available at this time, so do not try to set the value of that TextBox control, as it doesn't exist, yet. Fortunately, no event handlers can be attached to this event, anyway, so there isn't much you can do to customize this processing or to access that TextBox's value property; "Move along. There is nothing to see here." But, be aware that this event does occur after the Constructor, so if you try to access properties such as IsPostBack prior to the Start event, they have yet to be assigned, and will likely be incorrect. Page Initialization During page initialization, the controls are created, initialized, and added to the Page's controls collection. This is the first time that you can access a control by its UniqueID. Do note that all control properties are set to their code values, be it from code-behind or code-in-front, regardless of what may be available in ViewState and Form Post values. Control state has yet to be restored, so ViewState and Form Post values have not yet been pushed to the controls. Finally, Initialization (specifically, PreInit) is the only time that the Theme and Master Page can be programmatically modified. Page Load Page Load is where control state is restored. If the request is a PostBack, rather than a new request, all available property values are restored from ViewState and Form Post data and pushed to the applicable controls. Under most scenarios, this is where you're going to get what you need from the Database, such as pulling a value from the query string and loading an item with the matching identity. Validation The Validation event only applies to PostBack requests, and only when Validators are present in the control collection. The Validate method is executed for each Validator present, through which the IsValid property is set for each Validator. These IsValid property values are then cascaded up to the Page's IsValid property. Be aware that even if all Validators on the page are disabled, the Validation event will still fire; if a Validator is present, Validate is executed, without regard to any other property. Also, note that the Validation event is a child of the Page's Load event, so it is executed within the Page Load event chain, after Page Load, but prior to PostBack Events and LoadComplete. PostBack Events Once Validation is complete (if applicable), all PostBack events are executed, including the OnChange event of a DropDownList and the OnClick event of a command button. Post Back Events are also a child of the Page's Load event, executing after Validation and before LoadComplete. Render Finally, once all of the data is processed and Post Back events handled, the Page is rendered within the Web Browser. The Render event consists of saving all control property data to ViewState, processing the Page and each Control into HTML, and writing the HTML to the output stream. This is the last opportunity to modify the HTML output. Remembering the Order If you are having trouble remembering the order, instead try and remember this simple mnemonic: SILVER; Start, Initialize, Load, Validation, Events, Render. If you are doing a lot of ASP.NET programming, or anticipate that you will be in the near future, try to commit to memory the order of each of these events, and their scope of influence. Understanding these basic fundamentals of the ASP.NET Page Life Cycle will help ensure that you are executing your custom code at the right time, and in the right order, rather than stepping on yourself by conflicting with the core functionality. Now that we know the order of execution on Page Events, what is the order of the Controls? Does Page.Load execute before Control.Load? How about the order of sibling controls? What is the order of myTextBox1.TextChanged versus myTextBox2.TextChanged? Also, what are some things to look out for? As this series continues, we will discuss the details of event execution order within the ASP.NET Page Life Cycle, as well as some tips, trick, and traps when developing ASP.NET applications.
Next month, I will be speaking at CodeStock, a developer conference in Knoxville, Tennessee, held June 26-27. We will be discussing the ASP.NET Page Life Cycle, to help get over the fears and troubles with validation, event handing, data binding, and the conflicts between page load and page initialization. Dev Basics: The ASP.NET Page Life Cycle Jay Harris / Session Level: 100 When a request occurs for an ASP.NET page, the response is processed through a series of events before being sent to the client browser. These events, known as the Page Life Cycle, are a complicated headache when used improperly, manifesting as odd exceptions, incorrect data, performance issues, and general confusion. It seems simple when reading yet-another-book-on-ASP.NET, but never when applied in the real world. In this session, we decompose this mess, and turn the Life Cycle into an effective and productive tool. No ASP.NET MVC, no Dynamic Data, no MonoRail, no technologies of tomorrow, just the basics of ASP.NET, using the tools we have available in the office, today. It's a long drive from Michigan to Knoxville, but the conference is worth the trip (the first of two Tennessee conferences I will be attending this year). A few other local speakers will be making the trip to Knoxville, as well. Check out the full session list for more information, and while you are at it, register for the event if you haven't already done so; the cost is only $25 if you sign up before the end of May. I was there last year for the first CodeStock, and I had a great time; I'm excited about this years event, not only because I am speaking, but to see what other new things that people are talking about, catch up with friends, and to meet new people in the community. I hope to see you there.
Did you know that yourdomain.com and www.yourdomain.com are actually different sites? Are they both serving the same content? If so, it may be negatively impacting your search engine rankings. Subdomains and the Synonymous 'WWW' Sub-domains are the prefix to a domain (http://subdomain.yourdomain.com), and are treated by browsers, computers, domain name systems (DNS), search engines, and the general internet as separate, individual web sites. Google's primary web presence, http://www.google.com, is very different than Google Mail, http://mail.google.com, or Google Documents, http://docs.google.com, all because of subdomains. However, what many do not realize is that www is, itself, a subdomain. A domain, on its own, requires no www prefix; a subdomain-less http://yourdomain.com should be sufficient for serving up a web site. And since www is a subdomain, dropping the prefix could potentially return a different response. There are some sites that will fail to return without the prefix, and some sites that fail with it, but the most common practice is that the www subdomain is synonymous for no subdomain at all. The Synonymous WWW and SEO The issue with having two synonymous URLs (http://yourdomain.com and http://www.yourdomain.com) is that search engines may interpret them as separate sites, even if they are serving the same content. The two addresses are technically independent and are potentially serving unique content; to a cautious search engine, even if pages appear to contain the same content, there may be something different under the covers. This means your audience's search results returns two entries for the same content. Some users will happen to click on yourdomain.com while others navigate to www.yourdomain.com, splitting your traffic, your page hits, your search ranking between two sites, unnecessarily. HTTP Redirects will cure the issue. If you access http://google.com, your browser is instantly redirected to http://www.google.com. This is done through a HTTP 301 permanent redirect. Search Spiders recognize HTTP response codes, and understand the 301 as a "use this other URL instead" command. Many search engines, such as Google, will then update all page entries for the original URI (http://yourdomain.com) and replace it with the 301's destination URL (http://www.yourdomain.com). If there is already an entry for the destination URL, the two entries will be merged together. The search entries for yourdomain.com and www.yourdomain.com will now share traffic, share page hits, and share search ranking. Instead of having two entries on the second and third pages of search results, combining these entries may be just enough to place you on the first page of results. In addition to combining search entries for subdomains, you can also combine root-level domains through HTTP 301. On this site, in addition to adding the www prefix if no subdomain is specified, captainloadtest.com will HTTP 301 redirect to www.cptloadtest.com. Combining the Synonyms We need a way to implement an HTTP 301 redirect at the domain level for all requests to a site; however, often we are using applications that may not grant us access to the source, or we don't have the access into IIS through our host to set up redirects for ourselves. URL Rewrite, Part 2 covers a great drop-in redirect module by Fritz Onion that uses a stand-alone assembly with a few additions in web.config to HTTP 301 redirect paths in your domain (it also supports HTTP 302 redirects). This module is perfect for converting a WordPress blog post URL, such as cptloadtest.com/?p=56, to a DasBlog blog post URL like cptloadtest.com/2006/05/31/VSNetMacroCollapseAll.aspx. However, to redirect domains and subdomains, the module must go a step further and redirect based on matches against the entire URL, such as directing http:// to https:// or captainloadtest.com to cptloadtest.com, which it does not support. It's time for some modifications. private void OnBeginRequest(object src, EventArgs e) {
HttpApplication app = src as HttpApplication;
string reqUrl = app.Request.Url.AbsoluteUri;
redirections redirs
= (redirections) ConfigurationManager.GetSection("redirections");
foreach (Add a in redirs.Adds) {
Regex regex = new Regex(a.targetUrl, RegexOptions.IgnoreCase);
if (regex.IsMatch(reqUrl)) {
string targetUrl = regex.Replace(reqUrl, a.destinationUrl, 1);
if (a.permanent) {
app.Response.StatusCode = 301; // make a permanent redirect
app.Response.AddHeader("Location", targetUrl);
app.Response.End();
}
else
app.Response.Redirect(targetUrl);
break;
}
}
}
By converting app.Request.RawURL to app.Request.AbsoluteUri, the regular expression will now match against the entire URL, rather than just the requested path. There is one downside to this change: the value is the actual path processed, not necessarily what was in the originally requested URL. To this effect, the value of AbsoluteUri for requesting http://www.cptloadtest.com?p=56 is actually http://www.cptloadtest.com/default.aspx?p=56; by requesting the root directory, the default page is being processed, not the directory itself, so default.aspx is added to the URL. Keep this in mind when setting up your redirection rules. Also, the original code converted the URL to lower case; with my modifications, I chose to maintain the case of the URL, since sometimes case matters, and instead ignore case in the regular expression match using RegexOptions.IgnoreCase. Finally, I made some other minor enhancements, like using the ConfigurationManager, since ConfigurationSettings is now obsolete, and reusing the matching Regex instance for replacements.
Download: RedirectModule.zip
Includes:
- Source code for the drop-in Redirect Module
- Sample web.config that uses the module
- Compiled version of redirectmodule.dll
The code is based on the original Redirect Module by Fritz Onion and the Xml Serializer Section Handler by Craig Andera. As always, this code is provided with no warranties or guarantees. Use at your own risk. Your mileage may vary. Thanks to Fritz Onion for the original work, and allowing me extend his code further.
The usage is the same as Fritz Onion's original module. Drop the assembly into your site's bin, and place a few lines into the web.config. The example below contains the rules as they would apply to this site, 301 redirecting http://www.captainloadtest.com to http://www.cptloadtest.com, and adding the www subdomain to any domain requests that have no subdomain. <?xml version="1.0"?>
<configuration>
<configSections>
<section name="redirections"
type="Pluralsight.Website.XmlSerializerSectionHandler, redirectmodule" />
</configSections>
<!-- Redirect Rules -->
<redirections type="Pluralsight.Website.redirections, redirectmodule">
<!-- Domain Redirects //-->
<add targetUrl="captainloadtest\.com/Default\.aspx"
destinationUrl="cptloadtest.com/" permanent="true" />
<add targetUrl="captainloadtest\.com"
destinationUrl="cptloadtest.com" permanent="true" />
<!-- Add 'WWW' to the domain request //-->
<add targetUrl="://cptloadtest\.com/Default\.aspx"
destinationUrl="://www.$1.com/" permanent="true" />
<add targetUrl="://cptloadtest\.com"
destinationUrl="://www.$1.com" permanent="true" />
<!-- ...More Redirects -->
</redirections>
<system.web>
<httpModules>
<add name="RedirectModule"
type="Pluralsight.Website.RedirectModule, redirectmodule" />
</httpModules>
</system.web>
</configuration>
The component is easy to use, and can redirect your site traffic to any URL you choose. Neither code changes to the application nor configuration changes to IIS are needed. By using this module to combine synonymous versions of your URLs, such as alternate domains or subdomains, you will improve your page ranking through combining duplicate search result entries. One more step towards your own search engine optimization goals.
URL Rewrite
A few months ago I switched my blogging engine from WordPress to DasBlog, and was left with a pile of broken post links. The WordPress format of formatting URLs, such as cptloadtest.com/?p=56, no longer applied; DasBlog has its own way of building the URL: cptloadtest.com/2006/05/31/VSNetMacroCollapseAll.aspx. Other people, with links to my posts on their own blogs, and search engines, with search results pointing to the old format, no longer directed people to the proper page. Fortunately, the old WordPress way directed users to the domain root, so they saw the main page instead of a 404, but what I really needed was a way to get users to the content they came for. But, I wanted a way that didn't involve customizing DasBlog code. Part 1 discusses some client-side options for redirecting traffic. As a developer, I would only need to add some quick JavaScript or HTML to my pages to reroute traffic, but I wanted a better way. I wanted Google's links to be updated, rather than just for the link to correctly route. I want a solution that worked without JavaScript. I wanted a solution that didn't download the page twice, as any client-side processor would do. I wanted the HTTP 301. HTTP 301, Move Permanently, is a server-side redirection of traffic. No client-side processing is involved. And Google recognizes that this is a permanent redirect, as it's name implies, so the Google result link is updated to the new URL. But since this is a server-side process, and I don't have full access to IIS settings with my hosting provider, I needed some code. response.StatusCode = 301;
response.AddHeader("Location", "http://www.cptloadtest.com");
response.End();
You may have noticed that, unlike the title of this blog post, I did not use a URL rewrite. HTTP 301 (and 302) redirects are HTTP Response Codes that are returned to the client browser, and the client then requests the new URL. A URL Rewrite leaves the original URL in place, and renders a different page instead, without visibility to the client browser or end-user. If I were to URL rewrite my blog from page1.aspx to page2.aspx, the URL in your address bar would still list page1.aspx, even though the content was from page2.aspx. The end-user (and the search engine) never know about the URL for page2.aspx. However, with a HTTP redirect, the content and the address bar would both be page2.aspx.
I ran through Google to see if anyone had solved this problem in the past. I came across a Scott Hanselman post on HTTP 301, which mentions a fantastic drop-in redirect module by Fritz Onion. I was elated when I found this module, and it was very easy to implement. Drop the assembly into your application's bin, add a few lines into the web.config, and enjoy redirection bliss. The module also supports both HTTP 302 and HTTP 301 redirects through the "permanent" attribute in the redirection rules. <?xml version="1.0"?>
<configuration>
<configSections>
<section name="redirections"
type="Pluralsight.Website.XmlSerializerSectionHandler, redirectmodule" />
</configSections>
<!-- Redirect Rules -->
<redirections type="Pluralsight.Website.redirections, redirectmodule">
<!-- Wordpress Post Redirects //-->
<add targetUrl="/default\.aspx\?p=86"
destinationUrl="/2007/09/22/ManagingMultipleEnvironmentConfigurationsThroughNAnt.aspx"
permanent="true" />
<add targetUrl="/default\.aspx\?p=74"
destinationUrl="/2007/02/09/Flash8DepthManagerBaffledByThoseWithoutDepth.aspx"
permanent="true" />
<add targetUrl="/default\.aspx\?p=72"
destinationUrl="/2006/10/20/IE7Released.aspx"
permanent="true" />
<add targetUrl="/default\.aspx\?p=70"
destinationUrl="/2006/10/17/ClearingFlashIDEWSDLCache.aspx"
permanent="true" />
<add targetUrl="/default\.aspx\?p=69"
destinationUrl="/2006/10/10/CruiseControlNetV11ReleasedOct1.aspx"
permanent="true" />
<!-- ...More Redirects -->
</redirections>
<system.web>
<httpModules>
<add name="RedirectModule"
type="Pluralsight.Website.RedirectModule, redirectmodule" />
</httpModules>
</system.web>
</configuration>
The entire implementation took only a few minutes. It seemed like it took longer to upload the changes to the blog than it did to make the local edits. The solution is also very clean, as it required no modification to the existing application beyond integrating the configuration blocks into the web.config. Blog traffic will now seamlessly redirect from links that use the old WordPress URL to post that use the new DasBlog URL. Search Engines also update their links, and the page ranking for each post isn't lost into the ether just because the URL changed. All is well with the world, again.
Next up: Part 3. URL redirection also plays a role in Search Engine Optimization. In Part 3, I will go over some ways that you can use HTTP redirects to improve your search engine listings, as well as discuss some improvements I made to Fritz Onion's redirect module in the name of SEO.
URL Rewrite
A few months ago I switched my blogging engine from WordPress to DasBlog. Though I do feel that WordPress is a more mature engine, with a more robust feature set and a better plug-in community, I wanted a site that was built on .NET. Being a .NET developer, I wanted something I could tinker with, modify, and adjust to my liking. Redirecting WordPress URL Format to DasBlog One of the pain points of the switch was with the differences in how the two blogging engines generated their URLs. As an example, my post on the Collapse All macro for the Visual Studio Solution Explorer, originally written in WordPress, went from a URL of cptloadtest.com/?p=56 to cptloadtest.com/2006/05/31/VSNetMacroCollapseAll.aspx. A few sites, such as Chinhdo.com, link to the post's old URL, which without effort to put a redirect in place would return HTTP 404: File Not Found under the new engine. Enter URL redirects. I needed a way to translate one URL into another. More specifically, since WordPress retrieves all of its posts by query string (using the 'p' key), I needed to translate a specific query string key/value pair into a URL. But, I didn't want to have to recompile any code. There are a view client-side redirecting options available: Meta Refresh<meta http-equiv="refresh" content="0;url=http://www.cptloadtest.com/">
The Old-School HTML way of redirecting a page. Nothing but HTML is needed. Only access to the base HTML page is required. Place the above HTML within your <HEAD> tag, reformatting the content value with "X;url=myURL", where X is the number of seconds to wait before initiating the redirect, and myURL is the destination URL. Any browsers will obey Meta-Refresh, regardless of JavaScript's enabled setting, but the simple implementation brings with it simple features. Meta-Refresh will redirect any request the page, regardless of the query string, and there is no simple way to enable it only for the specific key/value query string pair. Because all WordPress requests are to the domain root--a single page--and referenced by query string, this would not work for my needs. If the old request was instead to a unique former page, such as /post56.html, I could go create a new copy of post56.html as just a stub, and have each page Meta-Refresh to the post's new location. But even if this did apply, it's too much work to create 56 stub pages for 56 former posts.
JavaScript Redirect<script type="text/javascript">
<!--
window.location = "http://www.cptloadtest.com/"
//-->
</script>
This one requires JavaScript to work. Since the query string is on the domain root, the request is serving up Default.aspx and applying the query string to it. DasBlog doesn't observe the "p" query string key, so it will not perform any action against it, but the query string is still available to client-side scripts. I can add JavaScript code to perform regular expression matches on the URL (window.location), and upon match rewrite the old URL to the new and redirect. It's relatively simple to create new match patterns for each of my 56 posts, and I can place all of my new client-side code atop Default.aspx. var oldPath = "/?p=56";
var newPath = "/2006/05/31/VSNetMacroCollapseAll.aspx";
if (document.URL.indexOf(oldPath) > 0)
{
window.location.replace(document.URL.replace(oldPath, newPath));
}
However, since the patterns are in client-side code, they are visible to end-users who view source. Users will also see page-flicker, as the Default.aspx is served up using the old URL, only to be redirected and refreshed under the new URL; a side-effect of downloading the page twice is that my bandwidth is also doubled for that single request, since users downloaded the page twice.
What it all means
All of the options above result in functionality similar a HTTP 302, otherwise known as a Temporary Redirect. This class of redirect is meant for simple forwarding or consolidation that is either of temporary nature or part of intended functional implementation. An example of where this would be used is if after one page is finished processing, another should be returned, such as if authentication is complete on a login page and the client should be redirected to a home page or landing page. With a 302, the original page is still a valid page, and users should still go to it in the future, but under certain scenarios there is an alternate page should be used.
The alternative to a 302 is a HTTP 301, otherwise known as Moved or a Permanent Redirect. The 301 is for files which have permanently changed locations and come with an implied "please update your links" notice. The HTTP 301 is ultimately what I was looking for. cptloadtest.com/?p=56 is a defunct URL that will never again see light of day; users, web sites, and (most importantly) search engines should update their references to the new DasBlog format of the post's URL. Client-side coding doesn't have the ability to create an HTTP 301, so it was beginning to look like I may either have to modify DasBlog code to get my 301s or live without. But, I found a way; this site now has all of the 301 goodness I craved, while keeping the DasBlog code pure.
It's all about HTTP Modules.
In Part 2, I will go over how to use HTTP Modules to implement both HTTP 301 and HTTP 302 redirects, all with simply dropping a file into your application bin and adding a new section to your web.config. No compile required.
URL Rewrite
Many .Net developers that have experience in presentation-layer development have previous exposure to this one, but to those who haven’t, I must share: Request.ApplicationPath is evil! Request.Application path should never be used. Ever. I hate it!
It’s misgivings are most commonly exposed when concatenating strings to the end of it to create a URL of some sort.
sURL = Request.ApplicationPath & “/someDirectory/somePage.aspx”
This is evil. Neither will work in all situations due to the method’s inconsistency with trailing slashes.
If the application root is in a sub-directory, say “http://server/myRoot/index.aspx”, then the appPath is “/myRoot”. Note: there is no slash at the end, and the above sURL gets a value of “/myRoot/someDirectory/somePage.aspx”.
If the application root is not in a sub-directory, say “http://server/index.aspx”, then the appPath is “/”. Note: there is a slash at the end, and the above sURL gets a value or “//someDirectory/somePage.aspx”. In this case, rather than requesting
“http://server/someDirectory/somePage.aspx”, wonderful Internet Explorer requests “http://somedirectory/somePage.aspx”. (I don’t fault IE for this. I commend it. It is actually one of the few cases where IE doesn’t kludge together a band-aid for Developer mistakes.)
So, don’t use Request.ApplicationPath. Ever. With no exception. You could make a method, perhaps MyUtilities.ApplicationPath, that checks to see if the return contains a trailing ‘/’, and if it does, give it the axe. This will turn your domain-root appPath to an empty string. Use your new method rather than Request.ApplicationPath, and all will be well with the world. But, don’t do it! Don’t make a new method. That just continues the evilness.
Use Page.ResolveURL(”~/someDirectory/somePage.aspx”) or the counterpart Control.ResolveURL if you want it to be relative to the control’s location. This is the only scenario you should use. ApplicationPath is evil!
NAnt hates .Net’s resource files, or .resx. Don’t get me wrong–it handles them just fine–but large quantities of resx will really bog it down.
Visual Studio loves resx. The IDE will automatically create a resource file for you when you open pages and controls in the ‘designer’ view. Back when we still used Visual SourceSafe as our SCM, Visual Studio happily checked the file in and forgot about it. Now, our 500+ page application has 500+ resource files. Most of these 500+ resource files contain zero resources, making them useless, pointless, and a detriment to the build.
This morning I went through the build log, noting every resx that contained zero resources, and deleted all of these useless files.
The compile time dropped by 5 minutes.
Moral of the story: Be weary of Visual Studio. With regards to resx, VS is a malware program that’s just filling your hard drive with junk. If you use resx, great, but if you don’t, delete them all. NAnt will love you for it.
“It compiles! Ship it!”
Microsoft has sent Visual Studio 2005 to the printers. That brings .Net 2.0 to the table in all of its glory. The official release date is still November 7, and though it is available now to all of us MSDN subscribers (though the site is too flooded to ping, let alone download), there is still some question on if the media will be ready in time to go in all of the pretty little VS05 boxes at your local Microsoft store.
The default settings of NUnit, TestRunner, and Test Driven Development all want different copies of the app.config at different locations. If ProjectName creates ProjectName.dll, then NUnit wants ProjectName.config, TR wants ProjectName.dll.config, and TDD wants TargetDir\ProjectName.dll.config. This is a lot of work to put in the post-build event of every unit test project, and can be even more work when another testing tool comes along that wants yet a new config filename. The best way to manage all of these file copies is through a common post-build event call.
Many probably opt for a NAnt script, but we found that passing in the required paths can sometimes cause NAnt to get confused, and it won’t properly parse the parameter listing. So, we went with a command file, instead.
CopyConfigs.cmd
rem for nunit
copy “%~1App.config” “%~1%~2.config”
rem for testrunner
copy “%~1App.config” “%~1%~2.dll.config”
rem for testdrivendevelopment
copy “%~1App.config” “%~3.config”
VS.Net Post Build Event
call “C:\MyPath\CopyConfigs.cmd” “$(ProjectDir)” “$(ProjectName) “$(TargetPath)”
VS.Net already includes a series of NAnt-like properties for project names, project directories, target [assembly] filenames, etc; these come in handy for creating a universal script. Placing the path references in quotes allows for spaces and other characters (Except more quotes) in the path. Executing the command file through a call allows us a little more versatility with the argument references (%~1 removes the surrounding quotes from the argument value, allowing us to append a few together without jacking the subsequent path).
In code deployment, it is often necessary to perform tasks that must be executed locally on the destination box, such as installing through an MSI or installing assemblies to the GAC through GACUtils. Thankfully, there is a way that this can all be done remotely, with any process, as long as it can be accessed through a command prompt. The destination computer will think that you are working on it directly, though it may just be your NAnt script doing the work for you.
SysInternals publishes a tool called PsExec. It allows you to execute a program remotely on a remote machine. To the remote machine the process is running locally. Because of this, you can use traditional command line tools to run programs and utilities, such as GACUtil to install a new assembly to the GAC on a remote box–a feature that most other options don’t support.
PSExec \MyServer "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\GACUtil.exe" "C:\bin\MyAssembly.dll"
Note: the paths are all ‘local’ paths on \\MyServer, just as you would enter from a command prompt in an RDP session to MyServer. You will also need to customize the paths to include whatever location and framework version your remote machine uses.
As for me, because our web applications make thorough use of the GAC, our only deployment method is a VS.Net Deployment project to create a MSI. We have NAnt scripts that upload the MSI to remote machines then execute PsExec run the installation (MSIExec) in unattended mode. It has brought our deployment time down from a manual 30-45 minutes to an automated 15 minutes, and allows code managers to spend those 30-45 minutes doing something else. We save even more time when we deploy to production, which contains an 8-server web farm.
Scott Hanselman has a good post today about the HttpOnly cookie attribute. It secures the cookie from access via the DOM. “The value of this property is questionable since any sniffer or Fiddler could easily remove it. That said, it could slow down the average script kiddie for 15 seconds.”
Read Scott’s full blog entry.
Here’s the meat-and-potatoes of what Scott came up with; it’s for your global.asax:
protected void Application_EndRequest(Object sender, EventArgs e)
{
foreach(string cookie in Response.Cookies)
{
const string HTTPONLY = ";HttpOnly";
string path = Response.Cookies[cookie].Path;
if (path.EndsWith(HTTPONLY) == false)
{
//force HttpOnly to be added to the cookie
Response.Cookies[cookie].Path += HTTPONLY;
}
}
}
|