|
.NET TIPS
[ASP.NET]Webページに含まれるリンクを動的に変更するには?[C#、VB]
デジタルアドバンテージ 岸本 真二郎
2008/01/24 |
|
|
SSLによりアクセスされる問い合わせフォームや申し込みフォームなどをWebサイトに含める場合、それらのページからSSLを用いない通常のページヘのリンクは、プロトコルを含めたリンクの記述が必要になる。
例えば、トップ・ページへ戻るリンクでは、単に「/index.html」と記述するのではなく、「http://xxx.com/index.html」のように、「http://xxx.com」の部分まで記述しなければならない。さもないと、そのトップ・ページへのリンクが「https://xxx.com/index.html」というURLでアクセスされてしまうためだ。
しかし当然ながら、通常はWebアプリケーションの開発中にドメイン部分(この例では「xxx.com」)を確定させることは難しく、またそのようにしてしまってはアプリケーションのテストがままならない。
そこで本稿では、サーバ側でリンク先のURL(<a>タグのhref属性の内容)を動的に変更する方法を紹介する。これは、.aspxファイルに含まれるリンクは通常どおりドキュメント・ルートを基準にしたパスを記述しておき、実際にブラウザにコンテンツを返す際に、Web.Configファイルなどであらかじめ設定しておいたルールに従って、必要に応じてリンク先に「http://www.xxx.com」といったプロトコルとドメイン名を追加するというものだ。
上記の例の場合、.asxpファイルで、
<a href="/index.html">トップ・ページ</a>
と記述している内容を、サーバ上で自動的に、
<a href="http://www.xxx.com/index.html">トップ・ページ</a>
に変換させることが可能だ。
このような変換には、ASP.NETにおいてブラウザへのレスポンスを示すHttpResponseクラス(System.Web名前空間)のFilterプロパティを利用する。このプロパティに独自に作成したフィルタを指定することにより、最終的にブラウザへ転送される内容(HTML)を自由に書き換えることができる。
ルールを決める
本稿では、フィルタにより書き換えを行うURLの情報をWeb.Configに記述することにする。ここでは、次のリスト1のように<Url>と<SslConvPath>という要素を用いて記述する。
<Url>要素は、非SSLのページにリンクする際のプロトコルとドメイン名の記述である。そして<SslConvPath>要素には、変換を行うパスをカンマで区切って記述する。ページ内のリンクで、これらのパスと一致するものがあれば、その先頭に<Url>要素の値を付加するというルールの記述である。
<?xml version="1.0"?>
<configuration>
<appSettings>
<Url>http://localhost</Url>
<SslConvPath>/index.html,/sitemap/,/news/,/press/</SslConvPath>
</appSettings>
</configuration>
|
|
リスト1 Web.configにURLを書き換える際のルールを記述しておく |
このリスト1の設定では、「/index.html」「/sitemap/」「/news/」「/press/」が含まれるリンクについて「http://localhost」を付加する。本番環境では、<Url>要素の内容を「http://www.xxx.com」といった適切なURLに書き換える必要がある。
そしてWeb.Configに記述した内容は、次のようなコードにより、アプリケーション開始時にApplicationオブジェクトなどで保持しておくようにする。
protected void Application_Start(Object sender, EventArgs e)
{
NameValueCollection appSettings =
System.Configuration.ConfigurationManager.AppSettings;
Application["Url"] = appSettings["Url"];
Application["SslConvPath"] = appSettings["SslConvPath"];
}
|
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
Dim appSettings As NameValueCollection = _
System.Configuration.ConfigurationManager.AppSettings
Application("Url") = appSettings("Url")
Application("SslConvPath") = appSettings("SslConvPath")
End Sub
|
|
リスト2 Webアプリケーション開始時の処理 |
書き換えを行うフィルタを作成
次に、フィルタとなるクラスを作成する(リスト3)。このクラスは、Streamクラス(System.IO名前空間)を継承したもので、Writeメソッドのみをオーバーライドすればよい(それ以外のメソッドは特に実装しない)。
フィルタ・クラスのコンストラクタでは、オリジナルのフィルタ(Streamオブジェクト)と、リンクの書き換えに必要なパラメータ(リスト1の情報)を取得している(このクラスのインスタンス化を行っている後述のリスト4を参照)。
オーバーライドしているWriteメソッドでは、第1パラメータで与えられた内容に「href=」という記述があるかを調べ、見つかった場合はそれに続くリンク先のパスをチェックし、適時変換を行いながら、オリジナルのStreamオブジェクトに出力する。
実際の.aspxファイルの記述では、「href」と「=」の間にブランクがあったり、大文字と小文字が混在したりする場合も考えられるので、状況に応じたフィルタ処理を行っていただきたい。
using System;
using System.IO;
using System.Diagnostics;
using System.Text;
namespace MyWebSite
{
public class SslFilter : Stream
{
private Stream m_stream;
private StreamWriter m_streamWriter;
private Decoder m_dec;
private string m_Url;
private Encoding m_enc;
private string[] m_convPaths;
// コンストラクタ
public SslFilter(Stream stm, string URL, string sConvPath)
{
m_enc = Encoding.GetEncoding("shift_jis");
m_stream = stm;
m_Url = URL;
m_streamWriter = new StreamWriter(m_stream, m_enc);
m_dec = m_enc.GetDecoder();
m_convPaths = sConvPath.Split(',');
}
public override void Write(byte[] buffer, int offset, int count)
{
int nCharCnt = m_dec.GetCharCount(buffer, offset, count);
char[] result = new char[nCharCnt];
int nDecoded = m_dec.GetChars(buffer, offset, count, result, 0);
char[] temp = new char[nCharCnt*2];
if (nDecoded <= 0)
return;
int cPos = 0;
for (int nPos=0; nPos<=nDecoded; ++nPos) {
bool bLastLine = nPos == nDecoded;
char c;
if (nPos < nDecoded) {
c = result[nPos];
} else {
c = '¥n';
}
temp[cPos++] = c;
if (c == '¥n') {
int len;
if (this.m_Url.Length > 0) {
string str;
str = new string(temp, 0, cPos);
foreach (string path in this.m_convPaths) {
string s= "href=¥"" + path;
if (str.IndexOf(s) >= 0) {
// リンク先を置換する
str = str.Replace(path, "#DUMMY#");
str = str.Replace("#DUMMY#", m_Url + path);
}
}
if (bLastLine) {
str = str.Substring(0, str.Length - 1);
}
str.ToCharArray().CopyTo(temp, 0);
len = str.Length;
} else {
len = cPos-1;
}
m_streamWriter.Write(temp, 0, len);
cPos = 0;
}
}
m_streamWriter.Flush();
} // end of Write()
#region Streamクラスで実装の必要なそのほかその他のメソッド/プロパティ
public override int Read(byte[] buffer, int offset, int count) {
throw new NotSupportedException(); }
public override bool CanRead {
get { return false; } }
public override bool CanSeek {
get { return false; } }
public override bool CanWrite {
get { return true; } }
public override long Length {
get { throw new NotSupportedException(); } }
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override void Flush() {
m_stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin) {
throw new NotSupportedException();
}
public override void SetLength(long value) {
throw new NotSupportedException();
}
#endregion
} // end of class
} // end of namespace
|
Imports System
Imports System.IO
Imports System.Diagnostics
Imports System.Text
Namespace MyWebSite
Public Class SslFilter
Inherits Stream
Private m_stream As Stream
Private m_streamWriter As StreamWriter
Private m_dec As Decoder
Private m_Url As String
Private m_enc As Encoding
Private m_convPaths As String()
' コンストラクタ
Public Sub New(ByVal stm As Stream, ByVal URL As String, ByVal sConvPath As String)
m_enc = Encoding.GetEncoding("shift_jis")
m_stream = stm
m_Url = URL
m_streamWriter = New StreamWriter(m_stream, m_enc)
m_dec = m_enc.GetDecoder()
m_convPaths = sConvPath.Split(","C)
End Sub
Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)
Dim nCharCnt As Integer = m_dec.GetCharCount(buffer, offset, count)
Dim result As Char() = New Char(nCharCnt - 1) {}
Dim nDecoded As Integer = m_dec.GetChars(buffer, offset, count, result, 0)
Dim temp As Char() = New Char(nCharCnt * 2 - 1) {}
If nDecoded <= 0 Then
Return
End If
Dim cPos As Integer = 0
For nPos As Integer = 0 To nDecoded
Dim bLastLine As Boolean = (nPos = nDecoded)
Dim c As Char
If nPos < nDecoded Then
c = result(nPos)
Else
c = Chr(10)
End If
temp(System.Math.Max(System.Threading.Interlocked.Increment(cPos),cPos - 1)) = c
If c = Chr(10) Then
Dim len As Integer
If Me.m_Url.Length > 0 Then
Dim str As String
str = New String(temp, 0, cPos)
For Each path As String In Me.m_convPaths
Dim s As String = "href=""" + path
If str.IndexOf(s) >= 0 Then
' リンク先を置換する
str = str.Replace(path, "#DUMMY#")
str = str.Replace("#DUMMY#", m_Url + path)
End If
Next
If bLastLine Then
str = str.Substring(0, str.Length - 1)
End If
str.ToCharArray().CopyTo(temp, 0)
len = str.Length
Else
len = cPos - 1
End If
m_streamWriter.Write(temp, 0, len)
cPos = 0
Else
End If
Next
m_streamWriter.Flush()
End Sub ' end of Write()
#Region "Streamクラスで実装の必要なそのほかのメソッド/プロパティ"
Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
Throw New NotSupportedException()
End Function
Public Overloads Overrides ReadOnly Property CanRead() As Boolean
Get
Return False
End Get
End Property
Public Overloads Overrides ReadOnly Property CanSeek() As Boolean
Get
Return False
End Get
End Property
Public Overloads Overrides ReadOnly Property CanWrite() As Boolean
Get
Return True
End Get
End Property
Public Overloads Overrides ReadOnly Property Length() As Long
Get
Throw New NotSupportedException()
End Get
End Property
Public Overloads Overrides Property Position() As Long
Get
Throw New NotSupportedException()
End Get
Set
Throw New NotSupportedException()
End Set
End Property
Public Overloads Overrides Sub Flush()
m_stream.Flush()
End Sub
Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long
Throw New NotSupportedException()
End Function
Public Overloads Overrides Sub SetLength(ByVal value As Long)
Throw New NotSupportedException()
End Sub
#End Region
End Class ' end of class
End Namespace ' end of namespace
|
|
リスト3 Streamクラスを継承した独自のフィルタ・クラス |
フィルタを設定する
最後に、作成したフィルタをページごとに設定する(リスト4)。.aspxファイルがリクエストされた際に最初に呼び出されるPage_Loadメソッドで、リスト3のフィルタ・クラスのインスタンスを生成し、HttpResponseオブジェクト(ページのResponseプロパティから取得可能)のFilterプロパティに設定する。
これでWebページの出力時にSslFilterクラスのWriteメソッドが呼び出されるようになり、リンクの変換が行われる。SSI(Server Side Include)を記述している場合は、SSIが展開された状態でWriteメソッドが呼び出されるので、SSIで指定したファイルに記述されたリンクも正しく変換される。
namespace MyWebSite
{
public partial class app : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// フィルタのインスタンス化
SslFilter fltr = new SslFilter(
Response.Filter,
(string)Application["SSLURL"],
(string)Application["SslConvPath"]);
// 独自のフィルタをセットする
Response.Filter = fltr;
}
}
}
|
Namespace MyWebSite
Public Partial Class app
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
' フィルタのインスタンス化
Dim fltr As New SslFilter( _
Response.Filter, _
DirectCast(Application("SSLURL"), String), _
DirectCast(Application("SslConvPath"), String))
' 独自のフィルタをセットする
Response.Filter = fltr
End Sub
End Class
End Namespace
|
|
リスト4 .aspxページがロードされた際の処理 |
リンク先を変更する以外にも
HttpResponseオブジェクトのFilterプロパティによる動的な内容の更新は、今回説明した<a>タグのリンク先を変更する以外にも、転送サイズを減らすためにフィルタで空白文字を除去するといったこともできる。また、Streamオブジェクトの内容をファイルに書き出すことで、ブラウザに返す内容そのものをログとしてサーバ側に残すといったことも可能だ。
カテゴリ:Webフォーム 処理対象:ページ
使用ライブラリ:HttpResponseクラス(System.Web名前空間)
使用ライブラリ:Streamクラス(System.IO名前空間)
|
TechTargetジャパン
Insider.NET フォーラム 新着記事
- 構文:インスタンス化と同時にプロパティを設定する (2016/2/24)
C#とVBで、クラスや構造体のインスタンスを作成するときに同時にそのプロパティやフィールドの値を初期化する方法を解説する
- package.json (2016/2/23)
package.jsonファイルは、Node.js+JavaScriptでのアプリ開発時に、そこで使用するパッケージやプロジェクト全体を管理するのに使われる
- SPA(シングルページアプリ) (2016/2/19)
SPAは単一ページで構成されるWebアプリであり、応答性がよく表現力の高いアプリをクロスプラットフォームでユーザーに提供できるのが利点だ
- WPF/UWP:コントロールのエッジをシャープに描画 (2016/2/17)
WPFアプリなどではコントロールのエッジがきれいに描画されないことがある。.NET 4で導入されたプロパティを使い、これを修正する方法を紹介する