WebSurfer's Home

トップ > Blog 2   |   ログイン
APMLフィルター

BlogEngine.NET 2.0 の Tag cloud の問題

by WebSurfer 2011年6月12日 21:19

Tag cloud の中のタグをクリックすると、通常はそのタグを持つ記事が選択されて表示されます。しかしながらバージョン 2.0 では、日本語のタグをクリックすると何も選択されない(従って表示もされない)という問題があります。

この問題は IE を使ったときに発生します。Firefox 4.0.1, Chrome 12.0.742.91, Opera 11.11 などでは問題ありませんでした。

原因は、IE が要求を Web サーバーに送るときクエリ文字列を URL エンコードしないためと、バージョン 2.0 の Core.Utils.RemoveIllegalCharacters メソッドで UrlEncode が HtmlEncode に変更されていたためです(SEO 対策だそうです)。

バージョン 2.0 の場合、Tag cloud の中に「日本語」というタグがあったとすると、そのリンクは以下のようになります(以下で、"日本語" は生の UTF-8 になります)。

http://surferonwww.info/BlogEngine2/?tag=/日本語

IE は、要求を送るときに、? の前までは URL エンコードしてから送信しますが、クエリ文字列はそのまま UTF-8 で送信します(バグ?)。それゆえ、サーバー側で Request.QueryString["tag"] では "/日本語" は正しく取得できません。

この問題を避けるため、tag も category と同様に、クエリ文字列ではなくファイル名に設定するように変更しました。以下のような感じです。

http://surferonwww.info/BlogEngine2/tag/日本語.aspx

こうすれば IE の場合でも "日本語" を URL エンコードしてから要求を送りますので、問題はなくなります。そのために変更しなければならないのは、概略以下の部分です。

  • URL 文字列を組み立ててタグのリンクに設定する部分
  • URL を書き換える UrlRewrite HTTP モジュール
  • 書き換えられた URL からタグ名を取得し関連する記事の一覧を表示する部分

具体的には以下のようにコードを変更しました。

widget/Tag cloud/widget.ascx.cs の LoadWidget メソッド

public override void LoadWidget()
{
  foreach (var key in this.WeightedList.Keys)
  {
    using (var li = new HtmlGenericControl("li"))
    {
      //li.InnerHtml = string.Format(
      //    LinkFormat,
      //    string.Format("{0}?tag=/{1}",
      //        Utils.RelativeWebRoot,
      //        Utils.RemoveIllegalCharacters(key)),
      //    this.WeightedList[key],
      //    "Tag: " + key,
      //    key);

      li.InnerHtml = string.Format(
        LinkFormat,
        string.Format("{0}tag/{1}", 
          Utils.RelativeWebRoot, 
          Utils.RemoveIllegalCharacters(key) + 
            BlogSettings.Instance.FileExtension),
        this.WeightedList[key],
        "Tag: " + Server.HtmlEncode(key),
        Server.HtmlEncode(key));

      this.ulTags.Controls.Add(li);
    }
  }
}

Core\Web\Controls\PostViewBase.cs の TagLinks メソッド

protected virtual string TagLinks(string separator)
{
  StateList<string> tags = this.Post.Tags;
  if (tags.Count == 0)
  {
    return null;
  }

  string[] tagStrings = new string[tags.Count];
  const string Link = "<a href=\"{0}/{1}\" rel=\"tag\">{2}</a>";

  //var path = Utils.RelativeWebRoot + "?tag=";
  //for (var i = 0; i < tags.Count; i++)
  //{
  //  var tag = tags[i];
  //  tagStrings[i] = string.Format(
  //    CultureInfo.InvariantCulture, 
  //    Link, 
  //    path, 
  //    HttpUtility.UrlEncode(tag), 
  //    HttpUtility.HtmlEncode(tag));
  //}

  string path = Utils.RelativeWebRoot + "tag";
  for (int i = 0; i < tags.Count; i++)
  {
    string tag = tags[i];
    tagStrings[i] = string.Format(
      CultureInfo.InvariantCulture, 
      Link, 
      path,
      Utils.RemoveIllegalCharacters(tag) + 
        BlogSettings.Instance.FileExtension, 
      HttpUtility.HtmlEncode(tag));
  }

  return string.Join(separator, tagStrings);
}

Core/Web/HttpModules/UrlRewrite.cs の RewriteTag メソッド

private static void RewriteTag(HttpContext context, string url)
{
  string tag = ExtractTitle(context, url);

  if (url.Contains("/FEED/"))
  {
    //context.RewritePath(
    //  string.Format("syndication.axd?tag={0}{1}", 
    //    tag, 
    //    GetQueryString(context)), 
    //  false);

    context.RewritePath(
      string.Format("syndication.axd?tag={0}{1}",
        context.Server.UrlEncode(tag), 
        GetQueryString(context)),
      false);
  }
  else
  {
    //context.RewritePath(
    //  string.Format("{0}?tag=/{1}{2}", 
    //    Utils.RelativeWebRoot, 
    //    tag, 
    //    GetQueryString(context)), 
    //  false);

    context.RewritePath(
      string.Format("{0}default.aspx?tag={1}{2}", 
        Utils.RelativeWebRoot,
        context.Server.UrlEncode(tag), 
        GetQueryString(context)), 
      false);
  }
}

default.aspx.cs の Page_Load メソッド

protected void Page_Load(object sender, EventArgs e)
{
  if (Page.IsCallback)
  {
    return;
  }

  if (Request.RawUrl.ToLowerInvariant().Contains("/category/"))
  {
    DisplayCategories();
  }
  else if (Request.RawUrl.
    ToLowerInvariant().Contains("/author/"))
  {
    DisplayAuthors();
  }
  // else if (Request.RawUrl.ToLowerInvariant().Contains("?tag="))
  else if (Request.RawUrl.ToLowerInvariant().Contains("/tag/"))
  {
    DisplayTags();
  }
  ....

default.aspx.cs の DisplayTags メソッド

private void DisplayTags()
{
  //if (!string.IsNullOrEmpty(Request.QueryString["tag"]))
  //{
  //  PostList1.ContentBy = ServingContentBy.Tag;
  //  PostList1.Posts = 
  //    Post.GetPostsByTag(Request.QueryString["tag"].
  //      Substring(1)).ConvertAll(
  //        new Converter<Post, IPublishable>(delegate(Post p) 
  //          { return p as IPublishable; }));
  //  base.Title = " All posts tagged '" + 
  //    Request.QueryString["tag"].Substring(1) + "'";
  //}

  if (!string.IsNullOrEmpty(Request.QueryString["tag"]))
  {
    PostList1.ContentBy = ServingContentBy.Tag;
    List<Post> posts =
      Post.GetPostsByTag(Request.QueryString["tag"]);
    PostList1.Posts =
      posts.ConvertAll(new Converter<Post, IPublishable>(
        p => p as IPublishable));
    if (posts.Count > 0)
    {
      foreach (string t in posts[0].Tags)
      {
        if (Utils.RemoveIllegalCharacters(t).Equals(
          Request.QueryString["tag"],
          StringComparison.OrdinalIgnoreCase))
        {
          base.Title = "
             All posts tagged '" + Server.HtmlEncode(t) + "'";
          break;
        }
      }
    }
    else
    {
      base.Title = 
        " All posts tagged '" + 
        Request.QueryString["tag"] + "'";
    }
  }
}

Core.Post.cs の GetPostsByTag メソッド

public static List<Post> GetPostsByTag(string tag)
{
  // RemoveIllegalCharacters not required
  //tag = Utils.RemoveIllegalCharacters(tag);

  var list =
    Posts.FindAll(
      p =>
        p.Tags.Any(t => Utils.RemoveIllegalCharacters(t).
          Equals(tag, StringComparison.OrdinalIgnoreCase)));

  return list;
}

Core\Web\Controls\PostViewBase.cs の DescriptionCharacters プロパティ

public int DescriptionCharacters 
{ 
  get 
  {
    int chars = 0;
    string url = 
      HttpContext.Current.Request.RawUrl.ToUpperInvariant();

    //if (url.Contains("/CATEGORY/") || url.Contains("?TAG=/"))
    if (url.Contains("/CATEGORY/") || url.Contains("/TAG/"))
    {
      if (BlogSettings.Instance.
        ShowDescriptionInPostListForPostsByTagOrCategory)
      {
        return BlogSettings.Instance.
          DescriptionCharactersForPostsByTagOrCategory;
      }
    }
    else
    {
      if (BlogSettings.Instance.ShowDescriptionInPostList)
      {
        return BlogSettings.Instance.DescriptionCharacters;
      }
    }
    return chars;
  }
}

User controls/PostList.ascx.cs の ShowExcerpt メソッド

private bool ShowExcerpt()
{
  string url = this.Request.RawUrl.ToUpperInvariant();

  //bool tagOrCategory = url.Contains("/CATEGORY/") || url.Contains("?TAG=/");

  bool tagOrCategory = 
    url.Contains("/CATEGORY/") || url.Contains("/TAG/");

  return BlogSettings.Instance.ShowDescriptionInPostList ||
    (BlogSettings.Instance.
        ShowDescriptionInPostListForPostsByTagOrCategory 
      && 
      tagOrCategory);
}

Tags:

BlogEngine.NET 2.0

BlogEngine.NET 2.0 の導入

by WebSurfer 2011年6月11日 16:00

今年の 1 月にリリースされた BlogEngine.NET バージョン 2.0 を導入しました。

BlogEngine.NET 2.0 の導入

と言っても、既存のバージョン 1.6.1 のブログ をアップグレードしたわけではなく、そこはそのまま残しておいて、BlogEngine2 という名前の別アプリケーションとして構築しました。

まだ試験段階ですので、試験のための記事しかありませんし、スタイルもデフォルトのままです。

今回は、データストアとして、今まで使っていなかった MySQL を使うことにしました(現在のホスティングサービスでは SQL Server と MySQL がそれぞれ 300MB ずつ使えます)。ちなみに、既存のブログ は SQL Server を使用しています。

Forms 認証にはサイト内で共通の SQL Server を利用した SqlMembershipProvider, SqlRoleProvider を使って、サイト内でアプリケーション間を移動するとき再認証を受けなくても済むようにしました。

とりあえず見つけた問題はすべて解決できましたので、どのような問題があったのか、どう解決したのかを備忘録として書いておきます。

データベース構築の問題

BlogEngine.NET/setup/MySQL フォルダの中にデータベースを構築するためのスクリプトファイルが用意されており、これを走らせて必要なテーブルを生成します。このスクリプトは各テーブルを DEFAULT CHARSET=latin1 で作るように指定しています。(latin1 が MySQL のデフォルトらしい)。

一方、MySQL 全体とデータベースのキャラクターセットはすべて UTF-8 で設定しており、その状態で日本語を使うと、Incorrect string value というエラーが出て投稿できませんでした。

結局 latin1 で作ったデータベースはドロップして、スクリプトの DEFAULT CHARSET=latin1 を utf8 に書き換えてテーブルを作り直して解決しました。

バージョン 1.6.1 の諸問題

以下の致命的な問題(詳細はリンク先の記事を参照ください)はすべて解消されていました。(1) については Encoding.Default が Encoding.UTF8 に変更されていました。(2), (3) は大幅にコードが書き換えられており、1.6.1 で問題だったコードは見つからず、実際に試して不具合は出ませんでした。

(1) 日本語の文字化け

(2) SyntaxHighlighter のコードの間違い

(3) reCaptcha のコードの間違い

以下は見栄えなどの軽微な問題です。1.6.1 の場合と同様な修正が必要でした。問題の詳細および修正方法はリンク先の記事を参照ください。

(4) 各記事の左上の日付の表示が 31. 5月 2010 12:15 のようになる

(5) Search の結果、タイトルの表示が「の検索結果 'XXXX'」となる

(6) 「関連するブログ」でテキストの折り返しがされない

(7) ウィジェットのカレンダーが月曜日から始まっている

(8) タイトルが 5月 31. 2011 のように表示される

以下の問題は 1.6.1 の場合(詳細はリンク先の記事を参照ください)より悪化していました。詳細は CodePlex の Discussions のページにアップしました。まだ検証中ですので、サーバーには修正済みファイルはアップしていません。検証が終わって修正版をサーバーにアップしたら、詳しい話を書きます。

(9) Tag cloud 中の日本語の Tag の問題

最後に、以下の問題は、SyntaxHighlighter のバージョンが変わったためか、解消されていました。

(10) SyntaxHighlighter とフラグメント識別子

------------ 2011/6/14 追記 ------------

上の (9) で書いた日本語の Tag の問題の解決策は BlogEngine.NET 2.0 の Tag cloud の問題 に詳細を書きました。興味がありましたらそちらを参照ください。

Tags:

BlogEngine.NET 2.0

こじはる語録

by WebSurfer 2011年5月29日 23:48

小嶋陽菜

円陣の最中に、こちらを見ているのは小嶋陽菜さんです。

小嶋さんを天才だと言う人がいます。冗談だと思っていたんですが、彼女が出演するテレビ番組などの受け答えを見て、ひょっとしたらそれは本当かもしれないと思い初めています。

絶妙な受け答え、飛んでるコメントなど、普通の人には考えつかないと思いました。まぁ、半分ぐらいはヤラセかもしれませんけど、残り半分ぐらいはヤラセではなさそうです。

という訳で、気に入ったこじはる語録をリストアップしてみました。(笑)

  • はるなも悩みとか相談したかった。でも、悩みなかった。
  • AKB48 に入って一番つらかったことは?「辛いことは忘れちゃうからないかなー」
  • 将来の夢は?「なんでもできる楽しい将来です」
  • 私は悩み全般に興味ないですね(笑)
  • 揚げ物の出来上がりの色を尋ねるのに 「本物キツネか? 絵のキツネか? どっちですか?」
  • 泣かぬなら私のせいだホトトギス。
  • 深みのある人間になりたいという、あっさーい目標を持っています(笑)
  • でも実は、私って "背中で見せるタイプ" だと思う。ただ誰も、私の背中を見ようとしないだけ。
  • 綿棒なんか持って、脳がかゆいの?
  • スタッフの人に、「あまりフラフラするな」って言われました。あ、フラフラ遊ぶなって意味じゃないですよ。風邪引いたり、事故に遭ったりしたらダメだぞ、っていうことです。
  • 朝最初にすることは?「アセる」
  • 個人的な抱負は?「ワタシは楽しくお仕事ができればいいです(笑)」
  • HDD は何の略かと聞かれて「はい。だいたい、だいじょうぶだと思います。」 「ま、皆さんもですねぇ、悩みとかいっぱいあると思うんですけど、だいたいのことは、なんとな~くだいじょうぶと思って、気をはらずに、頑張ってみてください。」(2012/3/9 追記)

Tags:

AKB48

About this blog

ここブログ2は日々の出来事、ブログ1はプログラミング関係のトピックスになっています。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar