ASP.NET WebForms UserControl с параметром универсального типа

В Visual Studio 2022 ASP.NET Web Application Project (.NET Framework) я, похоже, смог создать пользовательский элемент управления с помощью дженериков:

public class StronglyTypedListBox<T> : System.Web.UI.UserControl {
  protected ListBox lst; // This line actually exists in the .ascx.designer.cs file.
                         // I list it here for brevity.
  public GuardedList<T> Items {
    /* GuardedList<T> derives from System.Collections.ObjectModel.Collection<T>.
       lst.Items will be automatically populated and updated whenever Items are modified. */
    get { ... }
    set { ... }
  }
  /* Other constructs providing functionalities such as removing an item and moving an item up/down */
}

и разметка .ascx:

<%@ Control ...... Inherits = "MyProject.StronglyTypedListBox<T>" %>
<asp:ListBox id = "lst" runat = "server"></asp:ListBox>
<%-- Other markup providing functionalities such as removing an item and moving an item up/down --%>

Чтобы использовать этот «общий» пользовательский элемент управления на странице, я перетащил файл .ascx в разметку страницы (точно так же, как мы добавляем любой пользовательский элемент управления на страницу) и изменил его идентификатор на lst.

И я переместил следующую строку из файла .aspx.designer.cs

protected StronglyTypedListBox<T> lst;

в файл .aspx.cs и изменил его следующим образом:

protected StronglyTypedListBox<OneOfMyEntityType> lst;

Все вышеперечисленное, казалось, было в порядке в Visual Studio. Нет красных волнистых линий, и проект строится без сообщений об ошибках. Но при нажатии F5 на странице появляется исключение, в котором говорится, что ей не удается разобрать строку 1 .ascx, потому что не удалось загрузить тип MyProject.StronglyTypedListBox<T>.

Возможно ли то, чего я хочу достичь с помощью приведенных выше кодов? Если да, то что нужно исправить?

Я не понимаю, как ЛЮБОЙ подход к разработке программного обеспечения предполагает, что после того, как вы скомпилируете код, вы ЗАТЕМ измените несколько байтов в скомпилированном выводе. И я не понимаю, как ЛЮБАЯ практическая разработка программного обеспечения может предложить и принять идею о том, что после того, как вы создадите свой проект, вы должны изменить файл desinger.cs. Действительно, любое решение, основанное на такой парадигме, обречено — на 100% обречено. Что еще хуже, перетаскивание этого элемента управления на любую веб-страницу, конечно, ТОГДА сгенерирует новый фрагмент кода дизайнера для данной страницы. На самом деле, независимо от того, какое решение вы примете, эта идея никуда не годится.

Albert D. Kallal 08.12.2022 17:37

@AlbertD.Kallal Боюсь, вы что-то не поняли ... После компиляции/сборки ничего не меняется. Все шаги, которые я описал, - это время разработки в исходных кодах. .aspx.designer.cs является частью исходного кода, и в нем комментарии, которые генерирует Visual Studio, говорят, что вы можете переместить автоматически сгенерированное объявление поля (которое представляет дочерний элемент управления) из .aspx.designer.cs в .aspx.cs и изменить это если то, что сгенерировано VS, не соответствует вашим потребностям.

Jyunhao Shih 08.12.2022 23:47

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

Albert D. Kallal 09.12.2022 02:22

@AlbertD.Kallal Я думал об этом, чтобы - отказаться от универсального пользовательского элемента управления, вместо этого добавив к нему свойство Type для получения информации о типе... Но, как показывают коды в моем сообщении, пользовательский элемент управления использует GuardedList<T> . Я не знаю, как преобразовать объект Type в параметр универсального типа, чтобы GuardedList<T> мог его использовать...

Jyunhao Shih 09.12.2022 04:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как обсуждалось в комментариях, выйдите из дженериков, потому что aspnet_compiler.exe просто не поддерживает его. Вместо этого имейте свойство Type и используйте отражение, которое является ключом к решению.

Например, ниже показан пользовательский элемент управления, содержащий ListBox (с именем lst) с его ListItem, сопоставленными с коллекцией (определяемой как свойство Items) определенного типа (определяемой как свойство ItemType). То есть ListItems ListBox автоматически заполняются/добавляются/удаляются в соответствии с Items, а после обратной передачи Items могут быть перестроены из ListItems.

public partial class MappedListBox : System.Web.UI.UserControl {

  public Type ItemType {
    get => this._ItemType;
    set {
      if (this._ItemType != value && this._Items is object)
        throw new InvalidOperationException();
      this._ItemType = value;
    }
  }

  public Func<object, ListItem> ItemToListItem { get; set; }

  public Func<ListItem, object> ListItemToItem { get; set; }

  public IList Items {
    set {
      this.Items.Clear();
      if (value is object) foreach(var item in value) this.Items.Add(item);
    }
    get {
      if (this._Items is null) {
        if (this.ItemType is null) throw new ArgumentNullException(nameof(ItemType));

        //Recover entity objects from ListBox.Items as an IEnumerable<ItemType>
        var initialItems =
          typeof(Enumerable)
          .GetMethod(nameof(Enumerable.Cast))
          .MakeGenericMethod(this.ItemType)
          .Invoke(
            null
            , new[] {
                this.lst.Items.Cast<ListItem>().Select(i => this.ListItemToItem(i))
              }
          );
        
        //Rebuild this._Items as a GuardedList<ItemType>, filling it with initialItems,
        //and specify that addition/removal inside this list should automatically add/remove corresponding ListItems.
        //(This capability comes from System.Collections.ObjectModel.Collection<T>, which GuardedList<T> inherits.)
        var typeOfGuardedListOfItemType =
          typeof(GuardedList<>).MakeGenericType(this.ItemType);
        var guardedList =
          Activator.CreateInstance(
            typeOfGuardedListOfItemType
            , initialItems
            , (Action<int, object>)(
                (index, item) =>
                  this.lst.Items.Insert(index, this.ItemToListItem(item))
              )
            , (Action<int>)(index => this.lst.Items.RemoveAt(index))
            , (Action)(() => this.lst.Items.Clear())
            , (Action<int, object>)(
                (index, item) => {
                  var listItem = this.ItemToListItem(item);
                  this.lst.Items[index].Value = listItem.Value;
                  this.lst.Items[index].Text = listItem.Text;
                }
              )
          );
        this._Items = (IList)guardedList;
      }
      return this._Items;
    }
  }

  /* Constructs providing other functionalities such as moving an item up/down and so on */

  Type _ItemType;

  IList _Items;
}

И его можно использовать на странице или другом пользовательском элементе управления следующим образом:

public partial class EmployeeSelection : System.Web.UI.UserControl {
  // The MappedListBox instance is named lstSelected

  protected override void OnInit(EventArgs e) {
    this.lstSelected.ItemType = typeof(Employee);
    this.lstSelected.ItemToListItem = item => {
      var employee = (Employee)item;
      return new ListItem(employee.Name, employee.ID);
    };
    this.lstSelected.ListItemToItem = listItem => DB.Employees.Find(listItem.Value);
    
    this.EmployeeTreeView.SelectedNodeChanged += (s, ev) =>
      this.SelectedEmployees.Add(getChosenEmployeeFromTreeView());

    base.OnInit(e);
  }

  internal IList<Employee> SelectedEmployees {
    get => (IList<Employee>)this.lstSelected.Items;
    set => this.lstSelected.Items = value?.ToArray();
  }
}

Другие вопросы по теме