Пользовательский элемент управления ASP.NET. Какой лучший способ включить встроенную ссылку CSS только один раз?

Проблема: я встраиваю файл CSS в библиотеку пользовательских элементов управления с несколькими элементами управления. Я хочу использовать один и тот же файл CSS для всех элементов управления, независимо от того, сколько их экземпляров находится в заданной форме. Когда в форме находится более одного элемента управления, я бы хотел, чтобы в заголовке HTML страницы ASP.NET была ровно 1 ссылка на файл CSS.

Вот что я придумала (пока):

Public Sub IncludeStyles(ByVal Page As System.Web.UI.Page)
    'Don't include the reference if it already exists...
    If Page.Header.FindControl("MyID") Is Nothing Then
        Dim cssUrl As String = Page.ClientScript.GetWebResourceUrl(GetType(Common), StylePath)

        Dim css As New System.Web.UI.HtmlControls.HtmlGenericControl("link")
        With css
            .Attributes.Add("rel", "stylesheet")
            .Attributes.Add("type", "text/css")
            .Attributes.Add("href", cssUrl)
            .ID = "MyID"
        End With

        Page.Header.Controls.Add(css)
    End If
End Sub

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

Вопрос: что может быть лучше, если элемент управления заголовка добавляется в заголовок HTML только один раз?

Обратите вниманиеClientScript.RegisterClientScriptResource() Метод имеет параметр, который принимает тип .NET, и этот тип можно использовать для обеспечения вывода кода только один раз на страницу. К сожалению, этот метод работает только со ссылками на файлы JavaScript. Если есть встроенный эквивалент для ссылок CSS, это было бы моим предпочтением.

Обновить:

Я обнаружил немного более элегантный способ сделать этоВот используя Page.ClientScript.RegisterClientScriptBlock и сообщая, что вы включаете собственные теги сценария, однако, как указывает Рик, это не добавляет сценарий к тегу заголовка html и также не совместимо с xhtml.

Обновление 2:

Я увидел еще одну интересную идею оэта тема, но циклический просмотр коллекции элементов управления не очень хорошее решение и добавляет много накладных расходов, если у вас есть несколько ссылок и несколько элементов управления на странице.

Крис Лайвли придумал лучшее решение, которое требует меньше кода. Вот моя функция, измененная новым решением:

Public Sub IncludeStyles(ByVal Page As System.Web.UI.Page)
    If Not Page.ClientScript.IsClientScriptBlockRegistered(StyleName) Then
        Dim cssUrl As String = Page.ClientScript.GetWebResourceUrl(GetType(Common), StylePath)

        Dim css As New System.Web.UI.HtmlControls.HtmlGenericControl("link")
        With css
            .Attributes.Add("href", cssUrl)
            .Attributes.Add("rel", "stylesheet")
            .Attributes.Add("type", "text/css")
        End With

        Page.Header.Controls.Add(css)
        Page.ClientScript.RegisterClientScriptBlock(GetType(Page), StyleName, "")
    End If
End Sub

Несколько вещей, чтобы отметить об этом решении. В своем первоначальном посте Крис использовалIsClientScriptIncludeRegistered() метод, который не был соответствующим методомRegisterClientScriptBlock() метод. Чтобы сделать эту функцию правильно, тест должен быть сделан сIsClientScriptBlockRegistered().

Кроме того, обратите особое внимание на тип, который передаетсяRegisterClientScriptBlock(), Я передал пользовательский тип данных этому методу (одинаковый для всех моих элементов управления), но он не был зарегистрирован таким образом, чтоIsClientScriptBlockRegistered() тест будет работать. Для того, чтобы он функционировал, текущий объект Page должен быть передан как аргумент Type.

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

 jball06 окт. 2010 г., 22:35
Концептуально, почему CSS не является частью основного файла CSS сайта, который выбирается и кэшируется клиентом?
 daniatic30 окт. 2013 г., 11:03
W3validator не нравится не закодированный амперсанд, который возвращает GetWebResourceUrl. Чтобы исправить это, закодируйте URL с помощью Page.Server.HtmlEncode (url)
 NightOwl88806 окт. 2010 г., 23:27
Это контрольная библиотека, которую теоретически можно использовать на любом веб-сайте, и я предпочитаю использовать отдельный CSS (который можно легко переопределить) вместо жесткого кодирования всего встроенного CSS (как это часто делала Microsoft). Другими словами, мои элементы управления должны полностью не знать о сайте, на котором они размещены.

Ответы на вопрос(3)

Я не знаю почему, но ваше решение не сработало для меня, вызов ClientScript.IsClientScriptBlockRegistered всегда возвращал false. Но предложение Джона Бледсо по ссылке, которую вы уже предоставили (Вот) у меня сработало

public static void IncludeStylesheet(Page page, string href, string styleID)
{
    //Prevent the stylesheet being included more than once
     styleID = "_" + styleID;
    if (HttpContext.Current.Items[styleID] == null)
    {
        HtmlLink htmlLink = new HtmlLink();
        htmlLink.Href = href;
        htmlLink.Attributes.Add("rel", "stylesheet");
        htmlLink.Attributes.Add("type", "text/css");
        page.Header.Controls.Add(htmlLink);
        HttpContext.Current.Items[styleID] = true;
    }
}
 NightOwl88822 февр. 2013 г., 15:47
Это тоже интересная идея. Не уверен, почему я раньше не думал об использовании HttpContext.Items. Но спасибо, что поделились.
Решение Вопроса

Чтобы предотвратить дублирование при отправке файлов CSS из серверных элементов управления, мы делаем следующее:

if (!Page.ClientScript.IsClientScriptBlockRegistered("SoPro." + id)) {
    HtmlLink cssLink = new HtmlLink();
    cssLink.Href = cssLink.Href = Page.ClientScript.GetWebResourceUrl(this.GetType(), styleSheet);
    cssLink.Attributes.Add("rel", "stylesheet");
    cssLink.Attributes.Add("type", "text/css");
    this.Page.Header.Controls.Add(cssLink);
    this.Page.ClientScript.RegisterClientScriptBlock(typeof(System.Web.UI.Page), "SoPro." + id, String.Empty);
}

Сначала мы проверяем, был ли указанный сценарий ранее зарегистрирован. Если нет, мы добавим его.

 NightOwl88806 окт. 2010 г., 23:39
Интересная идея, и сначала я подумал, что это сработает. Однако, я попробовал это, и есть пара проблем (если я что-то пропустил). Это регистрирует CSS под типом mime "text / javascript". Кроме того, он создает тег «script» вместо тега «link». В-третьих, хотя это и не так важно, тег не включен в заголовок документа.
 NotMe07 окт. 2010 г., 15:12
@ NightOwl888: спасибо, что нашли это. Я обновляю нашу собственную кодовую базу. Я удивлен, что это не было обработано правильно раньше ..;)
 NightOwl88807 окт. 2010 г., 11:29
Отличная идея! Я добавил исправленную версию этого в свой оригинальный пост. Тест должен быть выполнен с RegisterClientScriptBlockInclude, но в остальном этот код прекрасно работает.
 NotMe07 окт. 2010 г., 00:08
К сожалению. Неправильный фрагмент. Посмотри снова.

Почему бы просто не создать частную логическую переменную в элементе управления, который вы установили в true при первоначальном создании CSS. Затем вы можете проверить это при вызове метода, чтобы увидеть, был ли уже установлен CSS. Пример ниже (мой VB ржавый, поэтому может быть немного ошибочным)

Private _hasCss As Boolean = False

Public Sub IncludeStyles(ByVal Page As System.Web.UI.Page)
    'Don't include the reference if it already exists...
    If Not _hasCss Then
        Dim cssUrl As String = Page.ClientScript.GetWebResourceUrl(GetType(Common), StylePath)

        Dim css As New System.Web.UI.HtmlControls.HtmlGenericControl("link")
        With css
            .Attributes.Add("rel", "stylesheet")
            .Attributes.Add("type", "text/css")
            .Attributes.Add("href", cssUrl)
            .ID = "MyID"
        End With

        Page.Header.Controls.Add(css)
        _hasCss = True

    End If
End Sub
 WDuffy07 окт. 2010 г., 12:24
Ты прав NightOwl, я полностью пропустил это!
 NightOwl88806 окт. 2010 г., 23:20
Не сработает Например, вы сделали переменную приватной - это означает, что у каждого экземпляра элемента управления будет отдельная копия. Совместно используемая переменная потенциально может иметь проблемы между разными пользователями, поэтому это тоже не сработает.

Ваш ответ на вопрос