SEF-адреса в ASP.NET 2.0 WebForms на IIS6: полное решение (работающий postback, ASP.NET AJAX)

01.11.2009 23:36 / Артём Волк / 5424 просмотра / ...

Задача реализации «красивых» URL (вида /catalog/notebooks/asus/) в ASP.NET 2.0 приложении, работающем на IIS6 требует нескольких дополнительных действий.

Настройка IIS

В случае, если нужно использовать т.н. extensionless URLs, т.е. адреса вида /catalog/notebooks/asus/, а не, например, /catalog/notebooks/asus.aspx) необходимое условие — настройка wildcard mapping'a в IIS.

Считается, что это несколько снижает производительность, т.к. теперь все запросы, включая запросы к статическим файлам, будут проходить через ASP.NET ISAPI-расширение, однако, другого способа на IIS6 нет.

Аналог mod_rewrite

Существует много реализаций самого механизма URL-реврайтинга, например, UrlRewritingNet. Компонент устанавливается как ASP.NET HTTP-модуль и конфигурируется через свою секцию в Web.config.

<urlrewritingnet rewriteOnlyVirtualUrls="true" contextItemsPrefix="QueryString" xmlns="http://www.urlrewriting.net/schemas/config/2006/07">
	<rewrites>
		<!-- Do not delete! -->
		<add name="RootOfTheSite" virtualUrl="^~/$" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/Default.aspx" ignoreCase="true"/>
		<!-- /Do not delete! -->
		<add name="TextPages" virtualUrl="^~/pages/(.+?)/?$" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/TextPagesPage.aspx?shortcut=$1" ignoreCase="true"/>
		...
	</rewrites>
</urlrewritingnet>

Сама процедура реврайтинга проблем не представляет, а вот часть отмеченная комментариями необходима, без неё при заходе в корень сайта IIS будет отдавать ошибку 404.

Postback

Следующей проблемой будет Postback, для решение этой проблемы можно использовать простой control adapter, который просто уберёт из тега <form>...</form>, оборачивающего всю страницу атрибут action="". Без указания этого атрибута postback будет идти на текущий адрес.

Решение состоит из двух компонентов, файла FormAction.browser (помещается в папку App_Browsers), с таким содержанием:

<browsers>
  <browser refID="Default">
	<controlAdapters>
	  <adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
			   adapterType="CrispStudio.Web.ControlAdapters.FormAction" />
	</controlAdapters>
  </browser>
</browsers>

И самого класса адаптера:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.Adapters;
using System.IO;

namespace CrispStudio.Web.ControlAdapters
{
	public class FormAction : ControlAdapter
	{
		protected override void Render(HtmlTextWriter writer)
		{
			base.Render(new FormActionWriter(writer));
		}
	}

	public class FormActionWriter : HtmlTextWriter
	{
		public FormActionWriter(HtmlTextWriter writer)
			: base(writer)
		{
			this.InnerWriter = writer.InnerWriter;
		}

		public FormActionWriter(TextWriter writer)
			: base(writer)
		{
			this.InnerWriter = writer;
		}

		public override void WriteAttribute(string name, string value, bool fEncode)
		{
			if (name != "action")
			{
				base.WriteAttribute(name, value, fEncode);            
			}
		}
	}
}

Автор решения — Scott Guthrie

Адреса на сайте

Для того, чтобы везде в ссылках можно было использовать относительные адреса удобно использовать тег <base /> в <head>. С этим тесно связана ещё одна проблема. Большинство ASP.NET-сайтов используют <head runat="server"> чтобы программно изменять <title>, в этом случае ASP.NET пытается «исправить» адреса CSS и JavaScript-скриптов в <head>, если они не абсолютные. Для решения проблемы нужно просто использовать компонент PlaceHolder:

<head runat="server">
<title></title>
<asp:PlaceHolder id="cphIncludes" runat="server">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />        
    <base href="<%=Utils.SiteUrl%>" />
		<link rel="stylesheet" type="text/css" href="css/styles.css" media="all"/>
		<script type="text/javascript" src="js/my_script.js" encoding="utf-8"></script>    	   
		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico" />        
	</asp:PlaceHolder>    
</head>

....
<img src="images/logo.png />

Генерация исходящих URL (потерянный фрагмент головоломки)

Один из вариантов генерации исходящих URL'ов — хранение форматов адресов в виде строк в ресурсах и подстановка нужных значений через String.Format(). Есть и более красивые варианты.