Можно ли записать сгенерированную упорядоченную последовательность чисел, охватывающую страницу содержимого и главную страницу, с помощью MVC / Razor? - PullRequest
3 голосов
/ 21 февраля 2011

Следующий пример кода - это упрощенная версия настройки, которую мы используем для рекламы на наших страницах, где мы должны написать номер позиции на каждом рекламном теге. Номера позиций должны быть последовательными сверху вниз, начиная с 1. Проблема заключается в том, что некоторые рекламные теги определены на главной странице / странице макета, а другие - в разметке страницы.

Пожалуйста, смотрите следующий пример кода:

_Layout.cshtml:

<!DOCTYPE html>
<html>
<head><title>Ad Position Test</title></head>

<body>
  @Html.SequentialNumber()
  @RenderBody()
  @Html.SequentialNumber()
</body>
</html>

Index1.cshtml:

@{Layout = "~/Views/Shared/_Layout.cshtml";}

@Html.SequentialNumber()

Index2.cshtml:

@{Layout = "~/Views/Shared/_Layout.cshtml";}

@Html.SequentialNumber()
@Html.SequentialNumber()

Helpers.cs

public static class HtmlHelperExtensions
{
    public static HtmlString SequentialNumber(this HtmlHelper html)
    {
        var tile = (int)(html.ViewData["Tile"] ?? 1);
        html.ViewData["Tile"] = tile + 1;

        return new HtmlString(tile.ToString());
    }
}

Вывод этой настройки для Index1.cshtml: «2 1 3» и для Index2.cshtml: «3 1 2 4». Это, конечно, результат выполнения метода RenderBody перед выполнением основного содержимого.

Итак, вопрос в том, как вывести правильно упорядоченную последовательность чисел, которая охватывает как главную страницу, так и страницу содержимого, так что выходные данные будут "1 2 3" и "1 2 3 4" соответственно?

Ответы [ 2 ]

3 голосов
/ 22 февраля 2011

Этого можно добиться, используя хитрые фильтры действий *

* Я хотел бы предвосхитить мой ответ тем фактом, что я не могу обязательно рекомендовать вам использовать метод, который я собираюсь опубликовать. Но мне было любопытно, можно ли это сделать, поэтому я и сделал. В комментариях ниже я рассмотрел более разумное решение с использованием управляемых обработчиков HTTP в IIS7, см. Также это удивительное руководство .

Поскольку, как вы обнаружили, вложенные представления отображаются в порядке их вложенности, вы не можете достичь требуемого эффекта во время обычной фазы отображения представления. Но после визуализации представления ничто не мешает нам изменить результирующую разметку (кроме, возможно, здравого смысла).

Итак, во-первых, давайте изменим помощник HTML, чтобы он испускал какой-то маркер, который мы можем заменить:

public static class HtmlHelperExtensions
{
    public static HtmlString SequentialNumber(this HtmlHelper html)
    {
        //Any sufficiently unique string would do
        return ":{ad_sequence}";
    }
}

В ASP.NET MVC вы можете украшать классы контроллера и действия контроллера атрибутами фильтра действий для выполнения кода до и после выполнения действий. Например, здесь мы определим фильтр действий для обработки всех методов внутри HomeController и только действия Index() соответственно:

[AdSequencePostProcessingFilter]
public class HomeController : Controller
{
}

public class HomeController : Controller
{
    [AdSequencePostProcessingFilter]
    public ActionResult Index()
    {
        return View();
    }
}

В ASP.NET MVC 3 у нас также могут быть глобальные фильтры , которые применяются ко всем действиям контроллера в вашем приложении:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalFilters.Filters.Add(new AdSequencePostProcessingFilterAttribute());
    }
}

Вы можете решить применить фильтр для отдельных действий, контроллеров или глобально. Ваш выбор.

Теперь нам нужно определить фильтр AdSequencePostProcessingFilterAttribute ( на основе этого класса фильтра кэширования ):

public class AdSequencePostProcessingFilterAttribute : ActionFilterAttribute
{
    private Stream _output;
    private const string AdSequenceMarker = ":{ad_sequence}";
    private const char AdSequenceStart = ':';

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        //Capture the original output stream;
        _output = filterContext.HttpContext.Response.Filter;
        filterContext.HttpContext.Response.Flush();
        filterContext.HttpContext.Response.Filter = new CapturingResponseFilter(filterContext.HttpContext.Response.Filter);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        //Get the emitted markup
        filterContext.HttpContext.Response.Flush();
        CapturingResponseFilter filter = 
            (CapturingResponseFilter)filterContext.HttpContext.Response.Filter;
        filterContext.HttpContext.Response.Filter = _output;
        string html = filter.GetContents(filterContext.HttpContext.Response.ContentEncoding);

        //Replace the marker string in the markup with incrementing integer
        int adSequenceCounter = 1;
        StringBuilder output = new StringBuilder();
        for (int i = 0; i < html.Length; i++)
        {
            char c = html[i];
            if (c == AdSequenceStart && html.Substring(i, AdSequenceMarker.Length) == AdSequenceMarker)
            {
                output.Append(adSequenceCounter++);
                i += (AdSequenceMarker.Length - 1);
            }
            else 
            {
                output.Append(c);
            }
        }

        //Write the rewritten markup to the output stream
        filterContext.HttpContext.Response.Write(output.ToString());
        filterContext.HttpContext.Response.Flush();
    }
}

Нам также понадобится приемник, где мы сможем захватить вывод:

class CapturingResponseFilter : Stream
{
    private Stream _sink;
    private MemoryStream mem;

    public CapturingResponseFilter(Stream sink)
    {
        _sink = sink;
        mem = new MemoryStream();
    }

    // The following members of Stream must be overriden.
    public override bool CanRead { get { return true; } }
    public override bool CanSeek  { get { return false; } }
    public override bool CanWrite { get { return false; } }
    public override long Length { get { return 0; } }
    public override long Position { get; set; }

    public override long Seek(long offset, SeekOrigin direction)
    {
        return 0;
    }

    public override void SetLength(long length)
    {
        _sink.SetLength(length);
    }

    public override void Close()
    {
        _sink.Close();
        mem.Close();
    }

    public override void Flush()
    {
        _sink.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _sink.Read(buffer, offset, count);
    }

    // Override the Write method to filter Response to a file. 
    public override void Write(byte[] buffer, int offset, int count)
    {
        //Here we will not write to the sink b/c we want to capture

        //Write out the response to the file.
        mem.Write(buffer, 0, count);
    }

    public string GetContents(Encoding enc)
    {
        var buffer = new byte[mem.Length];
        mem.Position = 0;
        mem.Read(buffer, 0, buffer.Length);
        return enc.GetString(buffer, 0, buffer.Length);
    }
}

И вуаля, у нас есть последовательность, увеличивающаяся в порядке документа: P

0 голосов
/ 21 февраля 2011

Вы можете принудительно ввести числа в функцию SequentialNumber (), которая идет перед RenderBody, и затем запустить последовательность оттуда. Я думаю, что есть более общее решение, но не могу думать ни о каком прямо сейчас: P

Редактировать Как насчет этого:

public static class HtmlHelperExtensions
{
    public static HtmlString SequentialNumber(this HtmlHelper html,int? sequence)
    {
        if(sequence!=null)
        {   
             var tile = sequence;
             html.ViewData["Tile"] = sequence + 1;
        }
        else
        {
             var tile = (int)(html.ViewData["Tile"] ?? 1);
             html.ViewData["Tile"] = tile + 1;
        }
        return new HtmlString(tile.ToString());
    }
}

А потом

<!DOCTYPE html>
<html>
<head><title>Ad Position Test</title></head>

<body>
  @Html.SequentialNumber(1)
  @Html.SequentialNumber(2)
  @RenderBody()
  @Html.SequentialNumber(null)
</body>

@{Layout = "~/Views/Shared/_Layout.cshtml";}

@Html.SequentialNumber(null)
@Html.SequentialNumber(null)

Я не уверен, что вы можете быть более универсальным, так или иначе, вы должны сказать своему помощнику, что один вызов имеет больший приоритет, чем другой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...