Thursday, January 5, 2012

Multiple SSL sites under one IIS

IIS7's GUI doesn't really let you bind multiple sites to the https protocol, but there is a way to do this.

First thing to do is bind a binding to a site in IIS that uses the certificate that you want to use. (Presumably this is your wildcard cert). This site will have the *:443 binding. This is necessary because this cert becomes the default cert used in the following instructions.

Open the command prompt and type this line:

cscript.exe C:\inetpub\AdminScripts\adsutil.vbs set /w3svc/[SiteIdentifier]/SecureBindings ":443:[HostHeader]"

[SiteIdentifier] is the ID detailed in IIS Manager (right click on a site name in IIS, Manage Website > Advanced Settings...)
[HostHeader] is your URL you want bound.

Also, it is important to note that you need to have IIS6 config compatibility component installed.

Here's how to do that:

To install the IIS 6.0 Management Compatibility Components by using the Windows Server 2008 Server Manager

  • Click Start, click Administrative Tools and then Server Manager.
  • In the left navigation pane, expand Roles, and then right-click Web Server (IIS) and select Add Role Services.
  • On the Select Role Services pane, scroll down to IIS 6 Management Compatibility.
  • Select the check boxes for IIS 6 Metabase Compatibility and IIS 6 Management Console.
  • Click Next from the Select Role Services pane, and then click Install at the Confirm Installations Selections pane.
  • Click Close to leave the Add Role Services wizard.

Tuesday, April 6, 2010

Change Page Title, Meta Keywords and Meta Description with jQuery

To change the page title use the following:
$("title").html([title])

For Meta Keywords and Meta Description:
$("meta[name = 'keywords']").attr("content", [keywords])
$("meta[name = 'description']").attr("content", [description])

Monday, February 15, 2010

Distinct? More like "dis stinks!"

So you are using LINQ and you have a list of of your class objects, but you need to be sure that you do not have duplicates in your list. You can't use the built in "Distinct" function because you have not built a comparator for your class. Should you iterate through all the element of the list while creating a temporary list? No. that's just dumb.

Do this instead: use GroupBy and then Select the first element of each group.

an example would look like this:

sampleObjects =
sampleObjects.GroupBy(d => d.SampleObjectId).Select(d => d.First());


Just be sure that on what ever field you perform the "GroupBy" menthod, it is the primary key property of that object.


Friday, January 15, 2010

Combine JavaScript and CSS files

Todays websites implement sophisticated design and functionality. That results in a lot of CSS and JavaScript included in the page content. More files means more requests and slower load time.

Here is simple solution - combine the resource files and compress them with GZip.

The first part of the article is about how to combine AJAX Control Toolkit resource files. The second part demonstrates how to combine JavaScript or CSS files, strip the white space, compress the result with GZip and cache it.

1. How to combine WebResource.axd and ScriptResource.axd files:

#region Using

using System;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Collections.Generic;

#endregion

///
/// Find scripts and change the src to the ScriptCompressorHandler.
///

public class ScriptCompressorModule : IHttpModule
{

#region IHttpModule Members

void IHttpModule.Dispose()
{
// Nothing to dispose;
}

void IHttpModule.Init(HttpApplication context)
{
context.PostRequestHandlerExecute += new EventHandler(context_BeginRequest);
}

#endregion

void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app.Context.CurrentHandler is Page && !app.Request.RawUrl.Contains("serviceframe"))
{
if (!app.Context.Request.Url.Scheme.Contains("https"))
{
app.Response.Filter = new WebResourceFilter(app.Response.Filter);
}
}
}

#region Stream filter

private class WebResourceFilter : Stream
{

public WebResourceFilter(Stream sink)
{
_sink = sink;
}

private Stream _sink;

#region Properites

public override bool CanRead
{
get { return true; }
}

public override bool CanSeek
{
get { return true; }
}

public override bool CanWrite
{
get { return true; }
}

public override void Flush()
{
_sink.Flush();
}

public override long Length
{
get { return 0; }
}

private long _position;
public override long Position
{
get { return _position; }
set { _position = value; }
}

#endregion

#region Methods

public override int Read(byte[] buffer, int offset, int count)
{
return _sink.Read(buffer, offset, count);
}

public override long Seek(long offset, SeekOrigin origin)
{
return _sink.Seek(offset, origin);
}

public override void SetLength(long value)
{
_sink.SetLength(value);
}

public override void Close()
{
_sink.Close();
}

public override void Write(byte[] buffer, int offset, int count)
{
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string html = System.Text.Encoding.Default.GetString(buffer);
int index = 0;
List list = new List();

Regex regex = new Regex("]*>[^<]*(?:)?", RegexOptions.IgnoreCase);
foreach (Match match in regex.Matches(html))
{
if (index == 0)
index = html.IndexOf(match.Value);

string relative = match.Groups[1].Value;
list.Add(relative);
html = html.Replace(match.Value, string.Empty);
}

if (index > 0)
{
string script = "";
string path = string.Empty;
foreach (string s in list)
{
if (path.Length + HttpUtility.UrlEncode(s).Length > 1800)
{
html = html.Insert(index, string.Format(script, path));

index += script.Length + path.Length - 3;
path = String.Empty;
}

path += HttpUtility.UrlEncode(s) + ",";
}

html = html.Insert(index, string.Format(script, path));
}

byte[] outdata = System.Text.Encoding.Default.GetBytes(html);
_sink.Write(outdata, 0, outdata.GetLength(0));
}

#endregion
}

#endregion
}

Web.Config settings:






2. How to combine script and stylesheet files:

<%@ WebHandler Language="C#" Class="ResourceHandler" %>

using System;
using System.Configuration;
using System.IO;
using System.Net;
using System.IO.Compression;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;

public class ResourceHandler : IHttpHandler {

#region Public Properties

public static bool UseFileSet
{
get { return Convert.ToBoolean(ConfigurationManager.AppSettings["UseFileSet"]); }
}

public static string VersionNo
{
get { return ConfigurationManager.AppSettings["VersionNo"]; }
}

public bool IsReusable
{
get { return true; }
}

#endregion

#region Private Methods

private static void Cache(TimeSpan duration)
{
HttpCachePolicy cache = HttpContext.Current.Response.Cache;

FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, duration);

cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(duration));
cache.SetMaxAge(duration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}

private static byte[] GZip(string source)
{
byte[] result;

using (MemoryStream ms = new MemoryStream())
{
byte[] buffer = System.Text.Encoding.ASCII.GetBytes(source);

GZipStream GZip = new GZipStream(ms, CompressionMode.Compress);

GZip.Write(buffer, 0, buffer.Length);
GZip.Close();

result = ms.ToArray();
ms.Close();
}

return result;
}

private static string Minimize(string source)
{
string result = Regex.Replace(source.Trim(), @"(\s+)|((\r?\n)+)|(\t+)", " ");

return result;
}

private static string RetrieveScript(string file)
{
string script = null;

try
{
Uri url = new Uri(file, UriKind.Absolute);

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.AutomaticDecompression = DecompressionMethods.GZip;

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
script = reader.ReadToEnd();
}
}
catch (System.Net.Sockets.SocketException)
{
// The remote site is currently down. Try again next time.
}
catch (UriFormatException)
{
// Only valid absolute URLs are accepted
}

return script;
}

#endregion

public void ProcessRequest(HttpContext context)
{
HttpRequest request = context.Request;
HttpResponse response = context.Response;

string root = request.Url.GetLeftPart(UriPartial.Authority);

string contetType = request.QueryString["type"] ?? "application/x-javascript";

string[] fileNames = new string[0];

if (!string.IsNullOrEmpty(request.QueryString["fileSet"]))
{
string fileSet = request.QueryString["fileSet"];
//Basic Validation
if (string.IsNullOrEmpty(fileSet))
return;

if (string.IsNullOrEmpty(contetType))
return;

string files = ConfigurationManager.AppSettings["FileSet_" + fileSet];

if (string.IsNullOrEmpty(files))
return;

//Get the list of files specified in the FileSet
fileNames = files.Split(',');
}
else if (!string.IsNullOrEmpty(request.QueryString["path"]))
{
string files = HttpUtility.UrlDecode(request.QueryString["path"]);

//Get the list of files specified in the FileSet
fileNames = files.Split(',');
}

if ((fileNames == null) || (fileNames.Length == 0)) return;

bool compress = Convert.ToBoolean(request.QueryString["compress"] ?? "true");

// Check whether GZip is supported by the client
string acceptEncoding = request.Headers["Accept-Encoding"];

bool gZip = (!string.IsNullOrEmpty(acceptEncoding) && acceptEncoding.ToLower().IndexOf("gzip") > -1);

//Set the content type
response.ContentType = contetType;

string content = String.Empty;

foreach (string fileName in fileNames)
if (!string.IsNullOrEmpty(fileName))
if (fileName.ToUpperInvariant().Contains("RESOURCE.AXD"))
content += RetrieveScript(root + fileName) + Environment.NewLine;
else if (File.Exists(context.Server.MapPath(fileName)))
content += File.ReadAllText(context.Server.MapPath(fileName)) + Environment.NewLine;

if (compress)
if (gZip)
{
response.AppendHeader("Content-Encoding", "gzip");
response.BinaryWrite(GZip(content));
}
else if (contetType != "application/x-javascript")
response.Write(Minimize(content));
else
response.Write(content);
else
response.Write(content);

response.End();

// Cache the resource for 10 minutes
Cache(TimeSpan.FromMinutes(10));
}
}

Web.Config Settings:






Combining AJAX Control Toolkit Resource Files

It is common to use quite a few AJAX Control Toolkit control on a single page. Each one creates a reference to a WebResource.axd or ScriptResource.axd files. Although they are small in size, the browser makes a separate request for each and every one of them. Given the limitations of only two concurrent connections/requests it results in a long load time.

The good news is there different solutions but essentially the result is the same.
1. Do it manually via IHttpModule and IHttpHandler
Read more

2. Use AJAX Control Toolkit

Just replace with in your ASPX page and that would do the trick. ToolkitScriptManager inherits from the ScriptManager therefore no errors will occur in the code behind.

A few properties that need to be included:
EnableScriptLocalization="true" EnablePageMethods="true" EnableScriptGlobalization="true" ScriptMode="Release" CombineScripts="true"

You might run into issue with turning on the CombineScripts="true". IE sometimes throws "Out of Memory" error message caused by infinite loop in the TextBoxWatermark Control JavaScript code:

AjaxControlToolkit.TextBoxWrapper.registerClass('AjaxControlToolkit.TextBoxWrapper', Sys.UI.Behavior);AjaxControlToolkit.TextBoxWrapper.validatorGetValue = function(id) { var control = $get(id);if (control && control.AjaxControlToolkitTextBoxWrapper) { return control.AjaxControlToolkitTextBoxWrapper.get_Value();} return AjaxControlToolkit.TextBoxWrapper._originalValidatorGetValue(id);}

The last line (which calls _originalValidatorGetValue) basically calls back this exact function over and over because control.AjaxControlToolkitTextBoxWrapper is undefined.

If you encounter the above error you should set the CombineScripts to "false".