キャッシュを使用してASP.Netのクラウドにタグを付けます。

暗い日曜日の朝、私は何もすることがなかったので、ASP.Netのタグクラウドである自分のバージョンのバイクを書くことにしました。 結果は非常に興味深いことが判明したので、記事の形に整理してHabréに掲載することにしました。

すぐに予約します-これはたった1時間半のコーディングの結果ですので、完全に準備の整ったコントロールとしてではなく、まだ開発および開発できる概念としてのみ認識してください。





だから。 タグクラウドについて何を知っていますか? タグクラウドは、<Number of Entries、Tag>値のペアで記述されるオブジェクトのセットです。 同様に、タグは<TagName、Link>のペアです。 そこで、タグに関する情報をカプセル化するクラスを作成しましょう。

public class Tag

{

public string Text { get ; set ; }

public string Href { get ; set ; }

}



* This source code was highlighted with Source Code Highlighter .






オカレンスの数はタグに関して外部エンティティのほうが多いため、ハエをカツレツから分離し、ペアの<int、Tag>オブジェクトで動作します。これは、KeyプロパティとValueプロパティが読み取り専用。 これは将来役に立つでしょう。 したがって、キープロパティにはタグの出現回数があり、それによってランク付けされ、値自体にはタグ自体がランク付けされます。

さて、毎分数百人がサイトを訪れていると想像してみましょう。 明らかに、タグクラウドの生成に時間がかからないようにする必要があります。 タグは非常に静的なものであり、N分ごとに1回以上クラウドを生成することはほとんど意味がありません。劇的に変更すべきではないからです。 そのため、キャッシュを整理する必要があります。 これらの考慮事項を念頭に置いて、コントロールを記述します。

[ToolboxData( "<{0}:TagCloudControl runat=server></{0}:TagCloudControl>" )]

public class TagCloudControl : WebControl

{

public const int MULTIPLIER = 2;

public const int MIN_SIZE = 5;



public event TagListDelegate TagsCollected;



protected override void Render(HtmlTextWriter writer)

{

writer.AddAttribute(HtmlTextWriterAttribute.Align, "Center" );

writer.AddAttribute(HtmlTextWriterAttribute.Width, Width.ToString());

writer.AddAttribute(HtmlTextWriterAttribute.Height, Height.ToString());

writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);

writer.RenderBeginTag(HtmlTextWriterTag.Div);



if (TagsCollected != null )

{

foreach ( var tag in TagCloudCache.GetTags(TagsCollected))

{

writer.WriteEncodedText( " " );

writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, string .Format( "{0}px" , (tag.Key+MIN_SIZE)*MULTIPLIER));

writer.RenderBeginTag(HtmlTextWriterTag.Span);

writer.AddAttribute(HtmlTextWriterAttribute.Href, tag.Value.Href);

writer.RenderBeginTag(HtmlTextWriterTag.A);

writer.WriteEncodedText(tag.Value.Text);

writer.RenderEndTag();

}

}



writer.RenderEndTag();

}

}




* This source code was highlighted with Source Code Highlighter .






すべてが非常に簡単です-抽象WebControlクラスから継承し、Renderメソッドをオーバーロードします。 デリゲートパラメーターがコントロールに渡されます

public delegate IEnumerable <Pair< int , Tag>> TagListDelegate();



* This source code was highlighted with Source Code Highlighter .






、タグとその出現に関する情報を含むペアのリストを返します。 キャッシュはデリゲートを見て、タグを再生成するかどうかを判断できる必要があります。 それで、生成されたタグクラウドに関する情報をカプセル化するクラスを書きましょう:

public class TagCalculationInfo

{

public TimeSpan TimeOut { get ; set ; }

public DateTime LastFiring { get ; set ; }

public IEnumerable <Pair< int , Tag>> CalculatedTags { get ; set ; }



public bool IsExpired

{

get

{

return DateTime .Now - LastFiring > TimeOut;

}

}

}



* This source code was highlighted with Source Code Highlighter .






クラスにはタイムアウトが含まれます-生成されたタグクラウドのライフタイム。この間、関連性があると見なされ、再生成する必要はありません。 LastFiringプロパティは、最後のタグの再生成の時間です。 CalculatedTags-タグ自体。 IsExpiredプロパティは、タグが最後に再生成されてから必要な時間が経過するとtrueを返します。 これで、キャッシュを構築できるすべてのブリックができました。

public static class TagCloudCache

{

private const int TAG_GROUPS = 10;

private static readonly TimeSpan DEFAULT_TIMEOUT = new TimeSpan (0, 1, 0);

private static readonly Dictionary< string , TagCalculationInfo> m_Cache = new Dictionary< string , TagCalculationInfo>();



public static IEnumerable <Pair< int , Tag>> GetTags(TagListDelegate target)

{

lock (m_Cache)

{

string key = target.GetKey();

if (!m_Cache.ContainsKey(key))

{

m_Cache.Add(key, new TagCalculationInfo { TimeOut = DEFAULT_TIMEOUT });

}

var tagInfo = m_Cache[key];

if (tagInfo.IsExpired)

{

tagInfo.CalculatedTags = RecalculateTags(target);

tagInfo.LastFiring = DateTime .Now;

}

return tagInfo.CalculatedTags;

}

}

}



* This source code was highlighted with Source Code Highlighter .






したがって、キャッシュはまったく複雑ではありません-m_Cacheディクショナリには、すでに生成されたタグクラウドに関する情報が含まれています。 コントロールがGetTagsメソッドにアクセスするとき、キャッシュがキャッシュ内にあるかどうかを確認し、そうでない場合は空の情報を追加します。 次に、クラウドが古くなっているかどうかを確認します。 古くなっている場合は、更新して、最後の再生成の時刻を現在に設定します。 残念ながら、デリゲートのEqualsメソッドは常にfalseを返すため、ここではDictionary <TagListDelegate、TagCalculationInfo>を使用できないことに注意する必要があります。 拡張メソッドによって形成される文字列をキーとして使用することにしました。

public static string GetKey( this TagListDelegate del)

{

return string .Format( "{0}{1}{2}" , ((MulticastDelegate)del.Target).Method.DeclaringType, ((MulticastDelegate)del.Target).Method.Name, ((MulticastDelegate)del.Target).Method.MethodHandle.Value);

}



* This source code was highlighted with Source Code Highlighter .






テスト中、この行は「TagCloud.TagCloud.TagCloudCacheGetTestTags84221360」のようになりました。 残念ながら、.Netでメソッドの結果を適切にキャッシュする方法に関する情報はインターネット上で見つかりませんでした。そのため、誰かが実際のプロジェクトでこのメソッドを使用することに決めた場合は、慎重に考えてその方法をGoogleで調べる必要があります。

先に進みます。 入力データからタグのクラウドがどのように計算されるかを理解することだけが残っています。 さらに2つの方法:

private static IEnumerable <Pair< int , Tag>> RecalculateTags(TagListDelegate target)

{

var tags = new List <Pair< int , Tag>>(target());

var max = tags.Max().Key;

var min = tags.Min().Key;

var clusters = new int [TAG_GROUPS];

var step = (max - min)/(TAG_GROUPS - 1);

for ( int i = 0; i < TAG_GROUPS; i++)

{

clusters[i] = min + i*step;

}

foreach ( var tag in tags)

{

tag.Key = FindClosestPosition(clusters, tag.Key);

}

tags.Sort();

for ( int i = 0; i < tags.Count; i += 2)

{

yield return tags[i];

}

for ( int i = tags.Count % 2 == 0 ? tags.Count - 1 : tags.Count - 2; i >= 0; i -= 2)

{

yield return tags[i];

}

}



* This source code was highlighted with Source Code Highlighter .






RecalculateTagsメソッドは、タグをグループに分けます-10個あります。タグのオカレンスの最小数と最大数を見つけます。 クラスターの配列を最小から最大までの値で均等に埋めます。 各タグについて、最も近いグループを見つけます-クラスター配列がソートされているため、バイナリ検索を使用できます:

public static int FindClosestPosition( int [] arr, int key)

{

int h = arr.Length - 1, l = 0;

while (h - l > 1)

{

int m = (h + l)/2;

if (arr[m] > key)

{

h = m;

}

else

{

l = m;

}

}

if ( Math .Abs(arr[h] - key) < Math .Abs(arr[l] - key))

{

return h;

}

return l;

}





* This source code was highlighted with Source Code Highlighter .






これで、この機能を実装できます-タグのサイズをクラウドの端から中央に向かって大きくしたいです。 グループごとにタグを並べ替えます。 最初に、ペアの位置にあるすべてのタグを最小から最大の順に返します。次に、逆の順序でペアになっていないタグを返します。

少し残っています-何が起こったのかをテストします。 テストタグを持つデリゲートが返すページコードを使用して、ファイルにプロパティを追加します。

public TagListDelegate TestTags

{

get

{

return TagCloudCache.GetTestTags;

}

}



* This source code was highlighted with Source Code Highlighter .






タグのプレフィックスを登録します。

<%@ Register Assembly="TagCloud" Namespace="TagCloud.TagCloud" TagPrefix="cc" %>



* This source code was highlighted with Source Code Highlighter .






クラウドをページに追加します。

< cc:TagCloudControl runat ="server" Name ="TagCloudControl" OnTagsCollected ="TestTags" />



* This source code was highlighted with Source Code Highlighter .






実行して結果を取得します。



もちろん、ファイルを使用してコントロールを慎重に変更する必要がありますが、基本的な原則は変わらないでしょう。

可能性のあるエラーについて事前に謝罪-ロシア語はネイティブではありません。 まあ、一般的に、Habréに関する最初の記事:)



UPD:コントロールは次のHTMLコードを生成します。

< span style ="font-size:10px;" >< a href ="#" > PHP </ a > < span style ="font-size:10px;" >< a href ="#" > Delphi </ a > < span style ="font-size:10px;" >< a href ="#" > Internet </ a > < span style ="font-size:10px;" >< a href ="#" > Nemerle </ a > < span style ="font-size:10px;" >< a href ="#" > Outsourcing </ a > < span style ="font-size:10px;" >< a href ="#" > VB.Net </ a > < span style ="font-size:10px;" >< a href ="#" > JavaScript </ a > < span style ="font-size:12px;" >< a href ="#" > C++ </ a > < span style ="font-size:12px;" >< a href ="#" > Apple </ a > < span style ="font-size:12px;" >< a href ="#" > Intel </ a > < span style ="font-size:14px;" >< a href ="#" > CLR </ a > < span style ="font-size:14px;" >< a href ="#" > Java </ a > < span style ="font-size:16px;" >< a href ="#" > WinForms </ a > < span style ="font-size:16px;" >< a href ="#" > Web </ a > < span style ="font-size:18px;" >< a href ="#" > WPF </ a > < span style ="font-size:22px;" >< a href ="#" > AJAX </ a > < span style ="font-size:28px;" >< a href ="#" > .Net </ a > < span style ="font-size:24px;" >< a href ="#" > ASP.Net </ a > < span style ="font-size:20px;" >< a href ="#" > C# </ a > < span style ="font-size:18px;" >< a href ="#" > Google </ a > < span style ="font-size:16px;" >< a href ="#" > MVC </ a > < span style ="font-size:14px;" >< a href ="#" > Microsoft </ a > < span style ="font-size:14px;" >< a href ="#" > SQL </ a > < span style ="font-size:12px;" >< a href ="#" > SEO </ a > < span style ="font-size:12px;" >< a href ="#" > jQuery </ a > < span style ="font-size:12px;" >< a href ="#" > Habrahabr </ a > < span style ="font-size:12px;" >< a href ="#" > Flash </ a > < span style ="font-size:10px;" >< a href ="#" > Sun </ a > < span style ="font-size:10px;" >< a href ="#" > LISP </ a > < span style ="font-size:10px;" >< a href ="#" > Facebook </ a > < span style ="font-size:10px;" >< a href ="#" > Perl </ a > < span style ="font-size:10px;" >< a href ="#" > RSDN </ a > < span style ="font-size:10px;" >< a href ="#" > Yandex </ a ></ span >





* This source code was highlighted with Source Code Highlighter .











All Articles