UserControl с шаблонами, изменение отображаемого идентификатора серверного элемента управления

TL;DR...

При создании [UserControl] с одноразовыми шаблонами можно ли заставить серверные элементы управления, размещенные в этих шаблонах, отображать их id без идентификаторов UserControl или контейнера?


В моем веб-приложении ASP.NET я создал UserControl с несколькими одноразовыми шаблонами (здесь я привел код только для одного из этих шаблонов)...

Public Class MyUserCtrl
    Inherits System.Web.UI.UserControl

    Private Class MyUserCtrlLiteralContainer
        Inherits Control
        Implements INamingContainer
    End Class

    <TemplateContainer(GetType(MyUserCtrlLiteralContainer)),
     TemplateInstance(TemplateInstance.Single),
     PersistenceMode(PersistenceMode.InnerProperty)>
    Public Property FirstTemplate As ITemplate

    Private Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Init
        If FirstTemplate IsNot Nothing Then
            Dim container As New MyUserCtrlLiteralContainer()
            FirstTemplate.InstantiateIn(container)
            plhFirst.Controls.Add(container)
        End If
    End Sub
End Class    

(Обратите внимание, plhFirst — это <asp:PlaceHolder> в разметке)

Это пример элемента управления, используемого на странице (управляется страницей Master)...

<asp:Content runat="server" ID="content" ContentPlaceHolderID="mainContent">
  <uc1:MyUserCtrl runat="server" id="muc1">
    <FirstTemplate>
      <asp:Button runat="server" id="btnMyButton" />
    </FirstTemplate>
  </uc1:MyUserCtrl>
</asp:Content>

Когда <asp:Button> отображается в HTML, id элемента приводит к...

id="ctl00_content_muc1_ctl00_btnMyButton"

... где второй ctl00 — это идентификатор экземпляра MyUserCtrlLiteralContainer.


Есть ли способ сделать так, чтобы id элемента управления отображалось следующим образом? (Поэтому идентификатор контейнера не используется)

ctl00_content_muc1_btnMyButton

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

ctl00_content_btnMyButton

В качестве альтернативы существует ли другой способ создания экземпляров элементов управления в UserControl, который привел бы к одному из перечисленных выше результатов?


person freefaller    schedule 05.07.2017    source источник
comment
Вам нужен MyUserCtrlLiteralContainer? (То есть, можете ли вы вместо этого создать экземпляр шаблона непосредственно внутри заполнителя?) И если вам нужен MyUserCtrlLiteralContainer, нужно ли реализовывать INamingContainer?   -  person Michael Liu    schedule 06.07.2017
comment
@MichaelLiu - еще раз спасибо за ваш вклад. Вы правы в своем предположении, что нет необходимости в дополнительных MyUserCtrlLiteralContainer, и вместо этого свойства можно настроить с помощью TemplateContainer(GetType(INamingContainer)) и создать экземпляр прямо в заполнителе... что приводит к ctl00_content_muc1_btnMyButton, что является большим шагом вперед. Я чувствую, что переход к ctl00_content_btnMyButton - это, вероятно, слишком далеко, но если у вас есть какие-либо идеи, пишите. А пока я предлагаю вам создать ответ с вашим предложением, чтобы я мог отдать вам должное, которого вы заслуживаете.   -  person freefaller    schedule 06.07.2017
comment
@MichaelLiu - Ах, нужно отметить, что если у вас (по чистой случайности) есть элемент управления в экземпляре UserControl с тем же именем, что и <asp:PlaceHolder> в разметке UserControl, он будет жаловаться на дубликаты идентификаторов - что справедливо   -  person freefaller    schedule 06.07.2017


Ответы (2)


Кнопка id включает muc1 и ctl00, так как UserControl и MyUserCtrlLiteralContainer реализуют INamingContainer. Если вы не хотите, чтобы id включало эти части, но вы нужно хотите, чтобы id включало другие идентификаторы предков, вы не можете использовать эти типы элементов управления.

Удаление ctl00: Если вам не нужен специальный элемент управления контейнером, вы можете удалить MyUserCtrlLiteralContainer и просто создать экземпляр шаблона непосредственно в PlaceHolder.

Удаление muc1: вы должны переписать свой UserControl как серверный элемент управления, который не реализует INamingContainer.

Вот пример реализации:

<ParseChildren(True)>
Public Class MyUserCtrl
    Inherits Control

    Private plhFirst As PlaceHolder

    <TemplateInstance(TemplateInstance.Single),
     PersistenceMode(PersistenceMode.InnerProperty)>
    Public Property FirstTemplate As ITemplate

    Private Sub Page_Init(sender As Object, e As EventArgs) Handles MyBase.Init
        plhFirst = New PlaceHolder()
        Controls.Add(plhFirst)

        If FirstTemplate IsNot Nothing Then
            FirstTemplate.InstantiateIn(plhFirst)
        End If
    End Sub

    Protected Overrides Sub Render(writer As HtmlTextWriter)
        ' <div class="myUserCtrl">
        writer.AddAttribute(HtmlTextWriterAttribute.Class, "myUserCtrl")
        writer.RenderBeginTag(HtmlTextWriterTag.Div)

        ' <div class="firstControls">
        writer.AddAttribute(HtmlTextWriterAttribute.Class, "firstControls")
        writer.RenderBeginTag(HtmlTextWriterTag.Div)

        plhFirst.RenderControl(writer)

        ' </div>
        writer.RenderEndTag()

        ' </div>
        writer.RenderEndTag()
    End Sub
End Class

Примечание. Чтобы избежать возможных конфликтов идентификаторов, я намеренно оставил plhFirst.ID неустановленным.

person Michael Liu    schedule 07.07.2017
comment
Еще раз, вы мой личный герой - спасибо @Michael - это точно решает проблему. Я бы предпочел иметь быстрый и простой контроль над HTML (имея фактическую разметку, а не код), но это делает именно то, что мне нужно... еще раз спасибо :-) - person freefaller; 07.07.2017
comment
@freefaller: Если у вас много статического HTML, вы можете вызвать writer.Write("<big chunk of HTML>") вместо AddAttribute и RenderBeginTag. - person Michael Liu; 07.07.2017
comment
Хорошая мысль - да, это тоже работает - еще раз спасибо :-) - person freefaller; 07.07.2017

Я не верю, что вы сможете заставить идентификаторы выглядеть так, как вам хочется, потому что они были разработаны для отображения иерархии элементов управления, какими бы уродливыми они ни были.

Однако вы можете использовать статические идентификаторы для элемента управления, страницы или всего проекта, если хотите. Ознакомьтесь с этой полезной статьей ScottGu, опубликованной еще в 2010 году:

https://weblogs.asp.net/scottgu/cleaner-html-markup-with-asp-net-4-web-forms-client-ids-vs-2010-and-net-4-0-series

Вам нужен раздел Today’s Cleaner Markup Topic: Client IDs.

person Nate Anderson    schedule 05.07.2017
comment
Спасибо - я уже знаю о ClientIDMode, но, как вы уже сказали, он не будет включать родительскую структуру. - person freefaller; 05.07.2017
comment
можно подделать вручную, хотя это звучит совсем не весело - person Nate Anderson; 05.07.2017