.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オブジェクトの内容をファイルに書き出すことで、ブラウザに返す内容そのものをログとしてサーバ側に残すといったことも可能だ。End of Article

カテゴリ:Webフォーム 処理対象:ページ
使用ライブラリ:HttpResponseクラス(System.Web名前空間)
使用ライブラリ:Streamクラス(System.IO名前空間)

この記事と関連性の高い別の.NET TIPS
[ASP.NET]ページから生成されたソース・コードを見るには?
[ASP.NET]ポストとポストバックの違いは?
WebBrowserコントロールによりWebページからリンクや画像を抽出するには?
ClickOnceアプリの起動時にパラメータを渡すには?
[ASP.NET]任意の画像を出力してブラウザで表示するには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

Insider.NET 記事ランキング

本日 月間
ソリューションFLASH