0

リンク先のサイトの正しいルック アンド フィールで .Net MVC Web アプリケーションをラップできる方法を開発したいと考えています。

基本的に、アプリケーションがマスター ページで使用するヘッダー/フッター HTML をスクリーンスクレイピングするために使用する包括的なサイトの「参照ページ」の URL を保存したいと考えています。

そのため、サイト (CMS からの出力) の構造/画像/色が変更された場合、アプリケーションは単に新しく作成された「テンプレート」を使用し、それに応じてそれ自体をラップします。

使用されている「テンプレート」には開始/終了の div タグが設定されているため、HTML をスクリーンスクレイピングし、関連するポイントで分割し、何らかの形でアプリの MasterPage に挿入するだけです。

スクリーンスクレイピングの部分はかなり単純に見えますが、それは私が整理するのに問題を抱えているマスターページへの挿入です。

どんな助けでも大歓迎です。:)

編集 - 私は現在これを頭の中で計画しており、投稿するコードはありません。私が言うように、スクリーンスクレイピング部分は問題ないように見えますが、ヘッダー/フッターの「参照ページ」から抽出された関連する HTML を、アプリケーションで使用されているマスター ページに挿入/挿入するにはどうすればよいでしょうか?

4

1 に答える 1

0

おそらく既に解決済みだと思いますが、マスター ページと MVC (および ASP.Net フォームも同様) で機能するソリューションを次に示します。

最初に、マスター ページの Render メソッドをオーバーライドし、RenderControl を使用して ContentPlaceHolders をレンダリングし、テンプレート内の特定のタグをレンダリング結果に置き換えてみました。これは ASP.Net フォームでは機能しますが、MVC では機能しません。この方法で<% using (Html.BeginForm("A","B")) { %>は常に、フォーム タグがページの一番上、Doctype の前にレンダリングされます。

解決

テンプレートを取得し、パーツに分割します。一部はテキスト パーツで、一部はプレースホルダー パーツです。マスター ページには、プレースホルダーだけでなく、HTML ドキュメントとプレースホルダーがあります。この方法では、VS デザイナーは文句を言いません。ただし、レンダリングするときは、まず Controls コレクションをクリアしてから、各パーツを LiteralControl または ContentPlaceHolder として追加します。実際のレンダリングは ASP.Net に任せるだけです。以下はインスピレーションのためのコードです。

マスター ページ:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title runat="server"></title>
    <asp:PlaceHolder ID="HeadPlaceHolder" runat="server">
        <script type="text/javascript" src="/cnnet/Resources/Js/jquery-1.8.1.min.js"></script>
    </asp:PlaceHolder>
    <asp:ContentPlaceHolder ID="HeadContentPlaceHolder" runat="server"/>
</head>
<body>
    <asp:ContentPlaceHolder ID="MainContentPlaceHolder" runat="server" />
</body>
</html>

マスター ページのコード ビハインド:

private HtmlHead originalPageHeader;
static readonly Regex HeadStartRegex = new Regex(@"^\s*<head[^>]*>");
static readonly Regex HeadEndRegex = new Regex(@"</head>\s*$");
static readonly Regex TitleRegex = new Regex(@"<title>[^<]*</title>");

public Default() { Init += Default_Init; }

private void Default_Init(object sender, EventArgs e) { DoScraping(); }

protected override void Render(HtmlTextWriter writer)
{
    // get content from html head control generated via Page.Header:
    string headHtml = RenderControl(originalPageHeader);
    Controls.Remove(originalPageHeader);
    headHtml = HeadStartRegex.Replace(headHtml, string.Empty);
    headHtml = HeadEndRegex.Replace(headHtml, string.Empty);
    headHtml = TitleRegex.Replace(headHtml, string.Empty);
    // head.Controls.Add(new LiteralControl(headHtml)); doesnt work if head content placeholder contains code blocks (i.e. <% ... %>)
    // Instead add content this way:
    int headIndex = Controls.IndexOf(HeadContentPlaceHolder);
    if (headIndex != -1)
        Controls.AddAt(headIndex + 1, new LiteralControl(headHtml));

    base.Render(writer);
}

private void DoScraping()
{
    IList<PagePart> parts = ... // do your scraping and splitting into parts
    Controls.Clear();

    foreach (PagePart part in parts)
    {
        var literalPart = part as LiteralPart;
        if (literalPart != null)
        {
            Controls.Add(new LiteralControl(literalPart.Text));
        }
        else
        {
            var placeHolderPart = part as PlaceHolderPart;
            switch (placeHolderPart.Type)
            {
                case PlaceHolderType.Title:
                    Controls.Add(new LiteralControl(HttpUtility.HtmlEncode(Page.Title)));
                    break;
                case PlaceHolderType.Head:
                    Controls.Add(HeadPlaceHolder);
                    Controls.Add(HeadContentPlaceHolder);
                    break;
                case PlaceHolderType.Main:
                    Controls.Add(new LiteralControl("<div class='boxContent'>"));
                    Controls.Add(MainContentPlaceHolder);
                    Controls.Add(new LiteralControl("<div/>"));
                    break;
            }
        }
    }
}

private string RenderControl(Control control)
{
    string innerHtml;
    using (var stringWriter = new StringWriter())
    {
        using (var writer = new HtmlTextWriter(stringWriter))
        {
            control.RenderControl(writer);
            writer.Flush();
            innerHtml = stringWriter.ToString();
        }
    }
    return innerHtml;
}

部品:

public class PagePart {}

public class LiteralPart : PagePart
{
    public LiteralPart(string text) { Text = text; }
    public string Text { get; private set; }
}

public class PlaceHolderPart : PagePart
{
    public PlaceHolderPart(PlaceHolderType type) { Type = type; }
    public PlaceHolderType Type { get; private set; }
}

public enum PlaceHolderType { Title, Head, Main }

分割の場合:

class PlaceHolderInfo
{
    public PlaceHolderInfo(PlaceHolderType type, Regex splitter)
    {
        Type = type;
        Splitter = splitter;
    }

    public PlaceHolderType Type { get; private set; }
    public Regex Splitter { get; private set; }
}

private static readonly List<PlaceHolderInfo> PlaceHolderInfos = new List<PlaceHolderInfo>
    {
        new PlaceHolderInfo(PlaceHolderType.Title, new Regex(TitleString)),
        new PlaceHolderInfo(PlaceHolderType.Head, new Regex(HeadString)),
        new PlaceHolderInfo(PlaceHolderType.Main, new Regex(MainString)),
    };

private static List<PagePart> SplitPage(string html)
{
    var parts = new List<PagePart>(new PagePart[] { new LiteralPart(html) });
    foreach (PlaceHolderInfo info in placeHolderInfos)
    {
        var newParts = new List<PagePart>();
        foreach (PagePart part in parts)
        {
            if (part is PlaceHolderPart)
            {
                newParts.Add(part);
            }
            else
            {
                var literalPart = (LiteralPart)part;
                // Note about Regex.Split: if match is found in beginning or end of string, an empty string is returned in corresponding end of returned array.
                string[] split = info.Splitter.Split(literalPart.Text); 
                for (int i = 0; i < split.Length; i++)
                {
                    newParts.Add(new LiteralPart(split[i]));
                    if (i + 1 < split.Length) // If result of Split returned more than one string, it means there was a match and we insert the placeholder between each string
                        newParts.Add(new PlaceHolderPart(info.Type));
                }
            }
        }
        parts = newParts;
    }
    return parts;
}

このソリューションは、より多くのプレースホルダー (ブレッドクラム、メニューなど) に簡単に拡張できることに注意してください。テンプレート内のプレースホルダーの順序やそれらの存在については想定していません。

編集1:私はもともとメソッドDoScrapingから呼び出しましたRender。Web フォーム (ctl00$MainContentPlaceHolder$RequestingRepeater$ctl01$ctl01 など) でコントロール名の再番号付けを行ったため、問題があることが判明しました。OnCommandリピーター内のボタンが機能しなくなるまで数字を台無しにしました。これを回避するには、コントロールの並べ替えをできるだけ早く行う必要があるため、現在に移動しましたInit

編集 2: 一部のページではPage.Header、スタイル タグとスクリプト タグの生成に使用されます。この機能をサポートするために、元の<head>タグを保持し、レンダリング時に生成されたコンテンツを挿入するためのいくつかのハックを追加しました。

于 2013-02-15T17:39:09.177 に答える