:::: MENU ::::

Thursday, September 26, 2013

“The fastest HTTP request is the one not made.” This simple rule can lead to faster page load times and less server load, without extensive performance optimization.

In the last post, we tried to reduce the number of HTTP requests by combining and deferring them. That's a good idea, but wouldn't it be even better to not even make the requests? If a resource is required, it must of course be downloaded by the client. If you make use of the browser's cache however, you can prevent the client from downloading static resources again and again. This way a user browsing your website must only load your global javascript and css files once, and when he returns later, he doesn't have to download them at all. For content which is more dynamic, you can use conditional GET requests which will get an empty result if the resource hasn't changed. This offers a nice trade-off between performance and freshness.

Prevent Requests with Cache Headers

To prevent request from being made multiple times, the headers Expires and Cache-Control: max-age can be used. They tell the browser how long the resource may be delivered from cache without another requests to the server. These two are redundant, they only differ in format. The Expires header defines a fixed date to which the resource may be cached, e.g. "Expires: Thu, 02 April 2014 14:21:12 GMT". The date must be formatted according to RFC 1123. In .NET you can use .ToString("r") on a DateTime object for that. The Cache-Control: max-age header on the other hand, defines how many seconds a resources may be delivered from cache, e.g. "Cache-Control: max-age=3600". TheCache-Control: max-age header was introduced in HTTP/1.1 and is not supported by some older browsers, so you are on the safe side if you set both, or only the expires headers. You can also control if the response should only be cached by the client (Cache-Control: private) or if it also can be cached by proxies (Cache-Control: public). If you want to prevent caching of a resource, you can use Cache-Control: no-cache or Cache-Control: no-store. According to the specifications no-cache should not prevent caching, but only allow serving from cache after a revalidation with the server. In reality, different browsers (or browser versions) interpret it inconsistently, so it is safer just to use no-store which indicates the resource should not be stored in a cache at all. For more detailed information about the cache-headers, check out the RFC

There are several ways to set cache headers. Which one is suitable depends on several parameters like the type of the resource (static vs. dynamic, content type etc.) or the flexibility you want to have. 

Setting Cache Headers in IIS

For static files directly deliverd by IIS, it can be as easy as adding some configuration to the Web.Config file. The following snippet allows caching of static files until May 2023.

<configuration>
    <system.webServer>
        <staticContent>
            <clientCache cacheControlMode="UseExpires" httpExpires="Mon, 01 May 2023 00:00:00 GMT" />
        </staticContent>
    </system.webServer> 
</configuration>

Setting Header Programmatically

You can also set the cache headers programmatically. This can be useful for generated content and allows more fine granular and flexible control of the cache headers. The example below allows clients as well as proxies to cache all responses for one hour. You can of course use different settings depending on any parameter/content/config value etc.

public class CachingModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += this.SetDefaultCacheHeader;
    }

    private void SetDefaultCacheHeader(object sender, EventArgs eventArgs)
    {
        HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
        HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.FromSeconds(3600));
        HttpContext.Current.Response.Cache.SetExpires(DateTime.UtcNow.AddSeconds(3600));
    }
    ...
}

Using the Output Cache

If you want to set cache headers per page/action, you can also just use the the output cache which we already discussed when looking at server side caching. The output cache not only stores the content of a page on the server, it also sets the corresponding cache headers. E.g. if you define a cache validity of one day for the output cache, the content will be stored on the server for one day and the cache headers will automatically be set to also allow clients to cache it for one day.

Far Future Expires Header

Static resources which do not change often should have a far future expires header. According to the specification, the date must not be farther in the future than one year, but it is a common practice to use even longer time spans. This is especially useful if the expiration date is defined in a static configuration and you don't want to adapt it continuously. 
Even if it does not happen often, stable resources may change from time to time. Therefor you need ensure that clients get the new version even if they have the old one in the cache. The easiest way to do that is to change the url of the resource, e.g. by adding a version number or a hash of the content. You could use a helper method like the one below to just add a hash to the query string. 

public static string ContentWithHash(this UrlHelper urlHelper, string contentPath)
{
    string path = urlHelper.Content(contentPath);

    IObjectCache cache = DependencyResolver.Current.GetService();
    string hash = cache.GetOrAdd(contentPath, () => GetMd5ForFileContent(urlHelper, contentPath));

    return string.Format("{0}{1}hash={2}", path, path.Contains("?") ? "&" : "?", hash);
}

Reduce Request Payload with Cache Headers

Even if you always want the client get the newest content, you can use cache headers to improve the performance. For this to work, you have to use the Last-Modified or the Etag header, which are used to determine if a resource has to be delivered again. In both cases the server includes a value in the response which the client sends along with the next request to the same resource. The server can then use the value to check whether the client already has the newest content, or if the content changed and has to be transferred again. If the client already has the newest content, the server sends an empty response with the status code 304 Not Modified. This saves computation time on the server as well as transfer time between server and client. Both results in a shorter load time for the client. The two headers are redundant, but google recommends the Last-Modified over the Etag header, as the browser might decide to not even send a request to the server if the resource hasn't changed in a long time. This sequence diagram shows two requests to the same resource, which has not changed between the requests and is therefor not delivered again.

You can use something like the following snippet to send 304 responses, you just need to include the Last-Modifiedheader when you deliver the content (e.g. in you controller or aspx page).



As mentioned at the beginning, cache headers can help to improve your website's performance with minimum effort. Make sure to at least go for the low hanging fruits by using standard features like the output cache or static cache headers in the web.config file.
The Web Deploy command line (msdeploy.exe) is located in "%programfiles%\IIS\Microsoft Web Deploy V2". This walkthrough will teach you how to utilize the command line in several common scenarios.
The main elements of a Web Deploy command line are a verb or operation, a source, an optional destination and optional operation settings.
MSDeploy.exe <-verb: name="">> <-source: object="">> [-dest:] [args…]
format:
Provider-type=[path-to-provider-object], [provider settings]

  • Scenario 1 - Sync IIS objects between two IIS 7 machines
There are 3 providers that can be used:
              1. appHostConfig provider, for the website
             2. webserver provider, for the whole webserver
             3. iisApp provider, for a single application
Typically, before doing an actual sync, you should run the command with the -whatif option to see what exactly will be synchronized.
Sync a single website
We first run the -whatif option.
msdeploy -verb:sync -source:apphostconfig="Default Web Site",computername=sourcemachine -dest:apphostconfig="Default Web Site" -whatif > change.log
Note that if you have applications within that website which are pointing to application pools different from the main website, the sync will fail because it is not able to find the right application pool. To overcome this, you can use the AppPoolExtension.
msdeploy -verb:sync -source:apphostconfig="Default Web Site",computername=sourceserver -dest:apphostconfig="Default Web Site" -enableLink:AppPoolExtension
You have to make sure that the destination machine has the Web Deployment Agent Service running.
The appHostConfig provider syncs the ApplicationHost.config and the site content. So, if you wanted to sync only the configuration and not the content, you would need to disable ContentExtension like this:
msdeploy -verb:sync -source:apphostconfig="Default Web Site",computername=sourceserver -dest:apphostconfig="Default Web Site" -enableLink:AppPoolExtension -disableLink:ContentExtension
This sync will also copy over the server certificate and server bindings if you have any.
You need to keep in mind that Web Deploy will not install any features for you. So if you have some extra features on the source server, the Dependency checker will mention that the feature is missing, but it will not go ahead and install it.
Sync two webservers
msdeploy -verb:sync -source:webserver,computername=sourceserver -dest:webserver
This command will synchronize the following:
1. applicationHost.config
2. Any custom appHost schema
3. Certificates
4. Content
5. GAC assemblies
6. Machine.config for both 32 bit and 64 bit frameworks
7. The root web.config for both 32 bit and 64 bit frameworks
If you want to exclude any of these, you can use the corresponding link extension. The following command will not sync the certificates between the source and destination:
msdeploy -verb:sync -source:webserver,computername=sourceserver -dest:webserver -disableLink:CertificateExtension
Sync an application
msdeploy -verb:sync -source:iisApp=Website\AppName,computerName=sourceServer -dest:iisApp=Website\AppName
This command will copy the "AppName" content over to the destination and mark it as an application. If a file or folder in the source does not exist on the destination, the provider creates the folder and any subfolders that have the corresponding files and attributes. If the destination folder already exists, the provider updates only those objects that do not match the source. Files on the destination that do not exist on the source will be deleted.
Note that the iisApp provider does not synchronize server or site-level configuration defaults. If the source and the destination configuration defaults are different, the synchronized application will inherit the application pool and other configuration defaults of its parent site on the destination.
  • Scenario 2 - Migrate a website from IIS 6.0 to IIS 7.0 
The main provider used is the metakey provider.
Typically, before doing the migration, you need to check the dependencies that need to be installed on the IIS 7 server. To get a list of dependencies that will be needed on the destination machine, run this command on the server which has the website to be migrated:
msdeploy -verb:getDependencies -source:metakey=lm/w3svc/1
You can use the resulting list of dependencies to setup the target machine.
The actual sync can be done like this:
msdeploy -verb:sync -source:metakey=lm/w3svc/1 -dest:metakey=lm/w3svc/2
The source and the destination metabase paths can be different as long as they are of the same type, like IISWebVirtualDirectory or IISWebServer.
In case you want to synchronize a website and include all the properties that the site inherits from the parent level, you can use this command:
msdeploy -verb:sync -source:metaKey=lm/w3svc/1,metaDataGetInherited=true -dest:metaKey=lm/w3svc/2
Note that you don’t need to install the IIS 6 compatibility feature on the IIS 7 machine since Web Deploy uses its own version of Admin Base Objects.


  • Scenario 3 - Sync GAC, registry keys and COM objects
1. Synchronizing GAC assemblies
msdeploy -verb:sync -source:gacAssembly="Assembly_name", computerName=source -dest:gacAssembly

You can also use the strong name of the assembly. The assembly name that you specify for the source and destination needs to be the same.
Note that the above command is useful only when the GAC assembly is installed on the source. If the assembly is not installed on the source but you want it setup in the GAC on the destination, you can use the gacInstall provider instead.
msdeploy.exe -verb:sync -source:gacInstall="c:\mybuild\My.Assembly.dll" -dest:gacInstall="My.Assembly",computername="Server2"
The above command will synchronize My.Assembly.dll from the local computer and install it in the GAC on server2.
Some caveats with the GAC providers are:
a. Satellite assemblies will not be moved
b. Only single-file GAC assemblies will be moved. If the assembly is multi-module, only the assembly manifest will be moved.
c. The provider does not detect the framework versions required by the assembly or bitness.
2. Synchronizing registry keys and values
To synchronize a registry key and its values, use the regKey provider:
msdeploy -verb:sync -source:regKey=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Inetmgr -dest:auto,computerName=Max
The above command will synchronize the entire Inetmgr registry key and all the sub-keys and values it has under it. The registry ACLs will be preserved.
To synchronize only a registry value, use the regValue provider:
msdeploy -verb:sync -source:regValue=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Inetmgr\Parameters\IncrementalSiteIDCreation -dest:auto,computername=Server3
3. Synchronizing COM objects
Depending on whether the COM component is 32 bit or 64 bit, use the comObject32 provider or the comObject64 provider.
comObject32:
msdeploy -verb:sync -source:comObject32="TestApp.MyCOMObject" -dest:auto, computername=Server2
comObject64:
msdeploy -verb:sync -source:comObject64="TestApp.MyCOMObject" -dest:auto, computername=Server2
The name of the COM component should be the same name that appears in the HKEY_CLASSES_ROOT hive of the registry.
  • Scenario 4 - Using manifests to sync multiple providers
A manifest is simply a group of providers that you put into a single definition file. By using the manifest provider, you can synchronize several things in one batch operation. The order in which you put the providers into the manifest file determines the order in which they will be synchronized.
The manifest is just an XML file that you can create using notepad. Here is a sample:





Save the file as Custom.xml and copy it to the web deploy installation directory. The example manifest above synchronizes the Default Web Site and its dependencies: a GAC assembly, some extra files, and an associated COM object.
To run the command, use the manifest provider, and point it to the Custom.xml file:
msdeploy.exe -verb:sync -source:manifest=custom.xml -dest:manifest=custom.xml,computername=Server1

  • Scenario 5 - Modifying sync behavior using rules
When Web Deploy is installed, it creates a file called msdeploy.exe.configsettings.example which contains a section called . The rules which have the isDefault attribute set to true are enabled by default and control how the sync operation works.
By default, no Msdeploy.exe.configsettings file exists, but you can create your own by renaming and editing the Msdeploy.exe.configsettings.example file.
You can also control the rules through command line. For example,
msdeploy -verb:sync -source:webServer -dest:webServer,computerName=Server2 -disableRule:SkipUNC
The above command will include UNC content in a sync operation by disabling the default rule SkipUNC. -enableRule enables a rule.
In addition to enabling and disabling rules, you can use the -skip and -replace switches to synchronize only some objects or to replace source information with something else on the fly.
1. Skip Rule
You can create a skip rule to skip an entry based on objectName, absolutePath, skipAction or xPath. Each entry in the source XML view is matched with available information in the skip rule. If a match is found, it is not considered for the sync operation.
This command will sync everything, but will skip the web.config file:
msdeploy -verb:sync -source:apphostconfig="Default Web Site",computername=sourceserver -dest:apphostconfig="Default Web Site" -enableLink:AppPoolExtension -skip:objectName=filepath, absolutePath=.*web\.config
Note that absolutePath is specified as a regular expression. You can also specify more complex regular expressions. For example:
msdeploy -verb:sync -source:apphostconfig="Default Web Site",computername=sourceserver -dest:apphostconfig="Default Web Site" -enableLink:AppPoolExtension -skip:objectName=filepath, absolutePath=.*folder\\subfolder\\.*\.gif$
This command skips all gif files under a folder.
You can use a negative lookahead regular expression as well.
msdeploy -verb:sync -source:dirPath=C:\inetpub\wwwroot -dest:auto -skip:objectName=filePath, absolutePath=\.(?!asp)
The above command will skip everything that is not ASP. So, essentially you are just syncing ASP files.
You can also add skip rules to the Microsoft.Web.Deployment.Config file.
For example, this rule will skip the synchronization of all registry keys under the registry key Inetinfo:
absolutePath="inetinfo" isDefault="true" />
2. Replace Rule
Replace rules can be created based on objectName, scopeAttributeName, scopeAttributeValue and targetAttributeName. The rule needs to have a replace clause and can have an optional match clause.
msdeploy -verb:sync -source:apphostConfig=”Default Web Site -dest:auto,computername=Destination -replace:objectName=virtualDirectory,match="c:\\content",replace="c:\content2"
The above command will sync the Content virtual directory on the source as Content2 directory on the destination.
You can also specify replace rules in the Microsoft.Web.Deployment.Config file. For example:

This rule replaces one IP address binding with another.
  • Scenario 6 - Offline syncing of servers
Sometimes the web servers that need to be synced may not be in the same domain, or they may not be online at the same time. There are 2 providers available which will let you sync servers which are offline. These are the archiveDir and package providers.
It is a 2 step process:
1. Create an archive on the source server like this:
msdeploy -verb:sync -source:apphostconfig="Default Web Site" -dest:archivedir=C:\archive
Note that this will archive the entire site content together with its config. The path needs to be an absolute path. In addition to the archived files, the archiveDir provider saves a file that is named Archive.xml file in the destination directory. The Archive.xml file lists, in XML format, all of the items that were included in the archive.
The archived directory needs to be copied to the destination server.
2. On the destination server, you can now synchronize from the archived directory.
msdeploy -verb:sync -source:archivedir=c:\archive -dest:appHostConfig="Default Web Site"
Instead of archiving a whole directory, you can use the package provider to compress the web content and place it in a .zip file called a package.
msdeploy.exe -verb:sync -source:contentPath="Default Web Site" -dest:package=c:\DWS.zip
On the destination, you can unpackage the contents of the zip file like this:
msdeploy.exe -verb:sync -source:package=C:\package.zip -dest:contentpath=C:\temp
Note that the source provider in the first command becomes the destination provider in the second command.

  • Scenario 7 - Syncing databases with Web Deploy
Web Deploy provides the SQL Database provider called dbFullSql for first-time installation or deployment of SQL databases to a specified destination database. Here are some ways in which you can use it:
1. Synchronize a single source database to an empty or non-existent database:
msdeploy.exe -verb:sync -source:dbFullSql="Data Source=.\SQLEXPRESS;Initial Catalog=SourceDatabase;Integrated Security=true" -dest:dbFullSql="Data Source=.\SQLEXPRESS;Initial Catalog=DestDatabase;Integrated Security=true"
2. Synchronize a database to an editable .sql file:
msdeploy.exe -verb:sync -source:dbFullSql="Data Source=.;Initial Catalog=SourceDatabase;Integrated Security=true" -dest:dbFullSql="d:\SourceDb.sql"
Now you can use the .sql file that was generated to sync to an existing database:
msdeploy.exe -verb:sync -source:dbFullSql="d:\SourceDb.sql" -dest:dbFullSql="Data Source=SqlMachine\SQLEXPRESS;Initial Catalog=DestDatabase;User Id=user1;password=blah"
3. Synchronize a database to an archive:
msdeploy.exe -verb:sync -source:dbFullSql="Data Source=.\SQLEXPRESS;Initial Catalog=SourceDatabase;Integrated Security=true" -dest:archivedir="c:\archive"
or synchronize it to a package:
msdeploy.exe -verb:sync -source:dbFullSql="Data Source=.\SQLEXPRESS;Initial Catalog=SourceDatabase;Integrated Security=true" -dest:package="c:\package.zip"
Note that the dbFullSql provider cannot be used for incremental publishing to a target database. Also, the dbFullSql provider operates at the database level only, and not at the SQL Server level. So, any objects at the server level, like logins, on which objects at the database level depend on (like users) will not be scripted.
  • Scenario 8 - Using Web Deploy to compare two websites
Server administrators often need to compare websites on different servers to find out what changes have been made to a site.
The first step would be to create a backup of the site, as in this example:
msdeploy -verb:sync -source:apphostConfig="Site1" -dest:archivedir=C:\Site1Archive
Now you can use the sync verb with the -whatif switch on another server (or against a later version of the same website) to see what would be updated if the operation were really done. Note that with -whatif, nothing is actually changed.
msdeploy -verb:sync -source:archiveDir=C:\Site1Archive -dest:apphostconfig="Site1" -whatif
The output will list exactly what would be synced. Since Web Deploy only synchronizes items that have changed, the output list is also a list of the items that have changed.
If you need more information about the exact differences, you can use the -verboseLevel flag, as in the following example:
msdeploy -verb:sync -source:archiveDir=C:\Site1Archive -dest:apphostconfig="Site1" -whatif -verboseLevel:Informational
Turning on this flag will give more information on exact differences. However, it also shows a lot of information about the dependency checking which occurs automatically during a sync. If you want to filter out this information, you can disable dependency rules by using -disableRule:Dependency* in the command:
msdeploy -verb:sync -source:archiveDir=C:\Site1Archive -dest:apphostconfig="Site1" -whatif -verboseLevel:Informational -disableRule:Dependency*
  • Scenario 9 - Transform Web.config with Web Deploy
Lets say that you have a web.config file which has a custom section like this:







During deployment, you want to change the UseCache value.
1. Create a package with -declareParam option:
msdeploy -verb:sync -source:apphostconfig="Default Web Site" -dest:package=sitepackage.zip -declareParam:name=UseCache, kind=XmlFile,scope=web.config,match="/configuration/myGroup/data/@useCache"
2. Now, when you deploy the package, use the -setParam option to specify a different value for UseCache:
msdeploy -verb:sync -source:package=sitepackage.zip -dest:auto -setParam:name=UseCache, value="false"
  • Scenario 10 - Using Parameter Files with Web Deploy
You can use parameter files if you have to do a large number of transformations during deployment. A parameter file is a XML file like this:













There are various kinds of parameters - DeploymentObjectAttribute, XmlFile, TextFile etc. TechNet has the documentation for each parameter kind.
When creating a package or archive, specify your parameter file with declareParamFile like this:
msdeploy.exe -verb:sync -source:apphostconfig="default web site/application" -dest:archivedir="C:\packages\application" -declareParamFile="C:\source\application\deployment\declareParamsFile.xml"
A parameters.xml file will be created in the package in the same format as the parameters file that you created.
Now you need to create a file which has the actual parameter values. It should be in this format:






For the purposes of this example, let's call this file setParams.xml.
Now, when you synchronize your package to your destination, use the setParamFile option to point to setParams.xml:
msdeploy.exe -verb:sync -dest:apphostconfig="default web site/application" -source:archivedir="C:\packages\application" -setParamFile="C:\source\application\deployment\setParams.xml"
  • Scenario 11 – Running tasks before and after a sync with Web Deploy
Sometimes you would like to run some scripts or batch files before and after a sync operation. Lets say that you want to stop a website while you are doing a sync so that it does not accept any requests and then start it back on once the sync is complete. You can use the runCommand provider with the –presync and –postsync arguments.
msdeploy –verb:sync –source:appHostConfig=MySite, computerName=sourceComputer –dest:auto –preSync:runCommand=”Appcmd stop site MySite & Appcmd set site MySite /serverAutoStart:false” -postAync:runCommand=”Appcmd start site MySite & Appcmd set site MySite /serverAutoStart:true”
Note that instead of specifying the Appcmd commands there, you can also create a batch file with the commands and specify the name of the batch file with runCommand.
One of the advantages of being able to run commands with Web Deploy is that you don’t need to be on the destination server to actually do it. These commands are being run remotely through Web deploy.

Tips for Using the Command Line
1) If the destination server does not have a valid certificate specified for the Web Management Service or the Remote Agent Service, Web Deploy will by default fail to connect with a certificate validation failure. Specify the –allowUntrusted flag on the command line to skip certificate validation.
2) If connecting over Web Management Service, specify the command line in with the wmsvc provider option:
Msdeploy.exe –verb:dump –iisApp=”Default Web Site”,wmsvc=DestinationServer,username=uid,password=pwd –allowUntrusted
This is the same as the more verbose command line below:
Msdeploy.exe –verb:dump –iisApp=”Default Web Site”,computername=”https://DestinationServer:8172/msdeploy.axd?Site=Default%20%web%20site”,username=uid,password=pwd,authType=basic –allowUntrusted
3) If connecting over the Remote Agent Service, specify the command in this format:
Msdeploy.exe –verb:dump –iisApp=”Default Web Site”,computername=DestinationServer,username=uid,password=pwd –allowUntrusted