by WebSurfer
2013年1月26日 17:09
WebResource.axd の HTTP 404 エラーのトラブルシューティングにつきいろいろ調べたので、今後役に立つかもしれない情報を備忘録として書いておきます。
まず、トラブルシューティングの情報で、ググって探した中でよさそうだと思ったページを以下に書いておきます。
Troubleshooting WebResource.axd
Web Resources Troubleshooting
前者のページでは、以下の事項がエラーの原因として挙げられています。自分もそう思います。
-
Missing compression exclusion
-
Slight error with compression module
-
Missing MachineKey / ValidationKey
-
Bad IIS setup, specifically the Application extension mapping
ただし、MSDN フォーラムで、.Net Framework 4 関連のアップデートを、すべてアンインストールしてから 再インストールしたら問題が解決したという話がありましたので、Windows Update の失敗(結果として MachineKey の設定が壊れる?)も原因として考えた方がよさそうです。
上記の 4 に関連して、"Verify that file exists" のチェックを外すよう、上に紹介したページに書かれています。その理由は、チェックを入れると iis は事前にファイルシステムに当該ファイルが存在するか確認するが、WebResource.axd というファイルはファイルシステムに存在しないのでエラーになる(結果 HTTP 404 を返す)ということだそうです。そのことは以下のページに詳しく書いてあります。
The Verify That File Exists Setting
なお、上記は iis6 の場合で、iis7 には "Verify that file exists" というチェックボックスは無いです。それに代わるものは以下のページを見てください。
Tip / Trick: how to turn off "verify file exists" in IIS7
iis7 マネージャーのハンドラマッピングの設定で、[要求のマップ先が次の場合のみハンドラを呼び出す(I)]にチェックを入れて、[ファイル(F)]を選択することが、iis6 で "Verify that file exists" にチェックを入れるのと同じことになるそうです。なので、下の画像のようにチェックを外す(resourceType="Unspecified" に設定する)必要があります。
ただ、デフォルトで間違いなく設定される(チェックは外れている)はずなので、自分でいじったりしなければ、これが 404 エラーの原因とは考えにくいですが。
404 エラーを返すのが特定の WebResource.axd のみの場合、その WebResource.axd に指定されているリソース(ファイル)を特定したい場合があると思います。
WebResource.axd のクエリ文字列のパラメータ d には、HTTP ハンドラが取得すべきファイルが指定されています。しかし、パラメータ d は暗号化されているため、そのまま見てもどのファイルを取得しようとしているかは分かりません。
パラメータ d の詳細については MSDN マガジンの ASP.NET AJAX アプリケーションの国際化 の「動作のしくみ」のセクションを見てください。
このページに "System.Web.UI.Page クラスの内部静的メソッド DecryptString および EncryptString を使用して、ページでこれらの文字列を暗号化および復号化できます" と書いてありますが、自分が調べた限りでは、Page クラスにはそのようなメソッドは公開されていません。
WebResource.axd のパラメータ d の値を復号する方法は、以下のページが参考になると思います。
Debugging ASP.NET 2.0 Web Resources:Decrypting the URL and Getting the Resource Name
そのページの下の方に "I am attaching a standalone page that you can drop in your application’s root and request it." とありますが、その a standalone page をクリックすると WebResources.aspx というソースファイルを入手できます。
入手できたら WebResources.aspx を問題の起こっている Web アプリケーションのルート直下に配置して使ってみてください。当方で検証した限りでは問題なく復号できました。
by WebSurfer
2012年12月16日 20:13
GridView も html にレンダリングされると table, tr, th, td などの要素になりますが、thead, tbody, tfoot 要素はデフォルトではレンダリングされません。今回は GridView で thead, tbody, tfoot 要素を追加する方法を書きます。
GridView は、内部で Table コントロール を利用しているようです。
従って、Table コントロール内の TableRow オブジェクトの TableSection プロパティ を TableRowSection 列挙体 のTableHeader, TableBody, TableFooter のいずれかに設定してやれば thead, tbody, tfoot 要素が追加されます。
以下のコードのような感じです。
実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。html ソースを見れば thead, tbody, tfoot 要素が追加されているのが分かると思います。
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
// データソース用の DataTable を作成
protected DataTable CreateDataTable()
{
DataTable dt = new DataTable();
DataRow dr;
dt.Columns.Add(new DataColumn("ID", typeof(Int32)));
dt.Columns.Add(new DataColumn("Name", typeof(string)));
dt.Columns.Add(new DataColumn("Price", typeof(Int32)));
dt.Columns.Add(new DataColumn("Qty", typeof(Int32)));
dt.Columns.Add(new DataColumn("Amount", typeof(Int32)));
dt.Columns.Add(new DataColumn("Note", typeof(string)));
for (int i = 0; i < 10; i++)
{
dr = dt.NewRow();
dr["ID"] = i;
dr["Name"] = "Name_" + i.ToString();
dr["Price"] = 123000 * (i + 1);
dr["Qty"] = (i + 1) * 20;
dr["Amount"] = 123000 * (i + 1) * (i + 1);
dr["Note"] = "Note_" + i.ToString();
dt.Rows.Add(dr);
}
return dt;
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GridView1.DataSource = CreateDataTable();
GridView1.DataBind();
}
}
protected void GridView1_RowCreated(
object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
e.Row.TableSection =
System.Web.UI.WebControls.TableRowSection.TableHeader;
}
else if (e.Row.RowType == DataControlRowType.DataRow)
{
e.Row.TableSection =
System.Web.UI.WebControls.TableRowSection.TableBody;
}
else if (e.Row.RowType == DataControlRowType.Footer)
{
e.Row.TableSection =
System.Web.UI.WebControls.TableRowSection.TableFooter;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="GridView1"
runat="server"
OnRowCreated="GridView1_RowCreated"
ShowFooter="True"
EnableViewState="False">
</asp:GridView>
</div>
</form>
</body>
</html>
by WebSurfer
2012年12月3日 21:37
外部スクリプトファイルを定義する script 要素 に defer="defer" 属性を追加すると、あるケースで、internet explorer (IE) がそのスクリプトファイルを解析できなくなるという問題の紹介です。
「あるケース」というのは、div 要素などの innerHTML を書き換えることです。自分でそのようなコートを書かなくても、例えば、SWFObject を使って Flash を埋め込む場合に innerHTML の書き換えが行われます。
ただし、html コードを書く順番が問題で、defer 属性を追加した script タグが出現した後、innerHTML を書き換える場合に限ります。順番が反対の場合は問題は起こりません。
確証がないのではっきりしたことは言えませんが、自分が試した限りでは、スクリプトの取得に時間がかかる(サーバーの応答が遅い)と問題が発生する確率が高いようです。ブラウザの解析の速度も関係があるようで、IE6 であればほぼ 100% 問題が発生するのに対し、IE8 は微妙なタイミングで問題が発生したりしなかったりします。
検証のため、スクリプトをダウンロードする HTTP ハンドラを作って、Thread.Sleep メソッドを使って応答に時間がかかるようにしてみました。
<%@ WebHandler Language="C#" Class="JavaScriptHandler" %>
using System;
using System.Web;
using System.Text;
using System.Threading;
using System.Diagnostics;
public class JavaScriptHandler : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
StringBuilder sb = new StringBuilder();
sb.Append("DateTime accessed: "
+ DateTime.Now.ToString("d MMM yyyy HH:mm:ss zzz",
System.Globalization.DateTimeFormatInfo.InvariantInfo)
+ ", ");
string delay = context.Request.QueryString["delay"];
int time;
bool result = Int32.TryParse(delay, out time);
if (result)
{
Thread.Sleep(time);
sb.Append(String.Format(
"delay time set: {0} ms", time) + ", ");
}
else
{
sb.Append("delay time set: none, ");
}
context.Response.ContentType = "text/javascript";
context.Response.Cache.VaryByHeaders["Accept-Encoding"] =
true;
context.Response.Cache.SetCacheability(
HttpCacheability.NoCache);
context.Response.Cache.SetExpires(
DateTime.Now.ToUniversalTime());
context.Response.Cache.SetMaxAge(
new TimeSpan(0, 0, 0, 0));
context.Response.AppendHeader("Pragma", "no-cache");
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
sb.Append(String.Format(
"TimeSpan measured: {0:000} ms", ts.Milliseconds));
string script = sb.ToString();
script = "var msg = '" + script + "'";
context.Response.Write(script);
}
public bool IsReusable
{
get
{
return false;
}
}
}
上記の HTTP ハンドラを呼び出す際、例えば、クエリ文字列を delay=200 とすると、リクエストを受けてから約 200ms 後にアクセスした時間、クエリ文字列の設定、実際に計った時間をスクリプトとして返します。
以下のような簡単な HTML コードで試すことができます。たぶん delay はもっと少なくても問題が再現すると思います。実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript"
src="JavaScriptHandler.ashx?delay=200" defer="defer">
</script>
<script type="text/javascript">
//<![CDATA[
function write(id){
document.getElementById(id).innerHTML =
"<h1>innerHTML changed!<\/h1>";
}
function ScriptTest() {
var x = msg;
alert(x);
}
//]]>
</script>
</head>
<body>
<div id="myContent">
<h1>This will be replaced by write method</h1>
</div>
<script type="text/javascript">
//<![CDATA[
write("myContent");
//]]>
</script>
<br />
<input type="button" value="Script Test"
id="button1" onclick="javascript:ScriptTest();" />
</body>
</html>
自分が検証した限りでは、IE6-9 で同じ問題が出ました(IE10 は未検証)。対応策は、(1) defer="defer" 属性を使用しない、または、(2) innnerHTML を書き換えた後で defer="defer" 属性付の script タグを読むよう順序を変更する、のいずれかしかなさそうです。