Практическое применение веб частей (исходники)

Источник: GOT DOT NET
Evgeniy Aristov

  1. Дизайнеру или зачем и кому нужны веб части
  2. Что же такое веб часть
  3. UserControl как веб часть
  4. Настройка веб части на странице
  5. Как работать с EditorLIart
  6. Усовершенствование внешнего вида. Chrome
  7. Внешние веб части
  8. Заключение
  9. Ссылки
  10. Благодарности

Дизайнеру или зачем и кому нужны веб части

Я думаю, вам знаком Google, my.yahoo.com, live.com, my.msn.com, и т.д. У них есть общая особенность - их посетитель может настраивать содержимое и внешний вид «своей» странички. В чем преимущество такой настройки? Обратимся к ассоциациям. Сайт с хорошо продуманной навигацией очень похож на мастерскую, где все лежит на своих местах. Если надо что-то достать - надо открыть сначала один ящик, потом второй, третий… Настроенная же страница - это уже готовые для работы инструменты, находящиеся под рукой. Хотя и в неком беспорядке, в котором может ориентироваться только его «создатель». Пример организации нескольких разделов форумов различных сайтов показан на рисунке 1 (Live.com).

рисунок 1

Рисунок 1

Для порталов «ящики» - это отдельно взятые сайты или их RSS каналы. Для сайтов - их разделы. Каждый постоянный посетитель сайта делит разделы по посещаемости - часто посещаемые, время от времени, мертвые зоны. Думаю, будет справедливо, если к наиболее востребованному содержимому доступ упроститься. А что нужно посетителю - знает только он сам. Для настройки под конкретного посетителя ASP.NET 2.0 готов предложить свой инструментарий - веб части.

Что же такое веб часть

Технология Microsoft позволяющая создавать настраиваемые страницы называется веб часть (Web parts). Типичная структура такой страницы изображена на рисунке 2.

рисунок 2

Рисунок 2

В Asp.Net 2.0 веб частью может быть любой элемент управления с обработкой на сервере. Для этого он должен находиться в зоне показа веб частей - WebPartZone или в одном из каталогов - CatalogZone. Для потомков System.Web.UI.WebControls.WebParts.WebPart доступна вся функциональность инфраструктуры веб частей. Если элемент управления не наследник WebPart - то элемент управления будет обернут в GenericWebPart : WebPart. Таким образом, инфраструктура веб частей работает с типом WebPart. Если потомка WebPart расположить вне зоны действия управления WebPartManager - то его поведение ничем не будет отличаться от обычного серверного элемента управления.

В SharePoint 2007 веб части построены на основе веб частей ASP.NET 2.0. Почти все классы из пространства имен Microsoft.SharePoint.WebPartPages наследники классов с соответствующими названиями из System.Web.UI.WebControls.WebParts. Возможности, которые даются веб частям в Microsoft.SharePoint.WebPartPages необязательны для работоспособности ASP.NET 2.0 веб частей в окружении SharePoint. Если вам самим не нужны эти возможности, то рекомендуется использовать ASP.NET 2.0 веб части.

UserControl как веб часть

Иногда удобно использовать как веб часть UserControl. Для него аналога класса WebPart, не предусмотрено, и функционирование как веб части ограничено. Поэтому можно создать свой абстрактный класс UserWebPart : UserControl, IWebPart, IWebEditable, IWebActionable. Именно отсутствие данных интерфейсов и ограничивает функциональность у не наследников WebPart. Роль каждого интерфейса в данном контексте приведена в таблице 1.

Таблица 1

Интерфейс

Роль

IWebPart Если элемент управления поддерживает данный интерфейс, но не является наследником WebPart, то элемент управления будет помещен в GenericWebPart, и значения свойств IWebPart будут соответственно присвоены экземпляру GenericWebPart.
IWebEditable Позволит для данного элемента управления создавать свой редактор свойств.
IWebActionable Позволит для данного элемента управления создавать свои действия над веб частью.

Примеры: WebUserControl.ascx, UserWebPart.cs

Настройка веб части на странице

Давайте рассмотрим простой пример того, что нужно сделать разработчику, что бы посетитель сайта смог настроить нашу веб часть. Оказывается, для этого от разработчиков требуется минимум усилий. А именно, пометить необходимые свойства атрибутом WebBrowseableAttribute и если мы хотим, что бы значение свойства сохранялось, помечаем его атрибутом PersonalizableAttribute. Звучит неплохо. И так:

private string _name = "noname";
//типичные атрибуты для custom control
[DefaultValue("noname")]
[Browsable(true)]
//значение свойства будет сохраняться для каждого пользователя персонально           
[Personalizable(PersonalizationScope.User)]
//значение свойства можно будет поменять в PropertyGridEditorPart 
[WebBrowsable(true)]
//описание свойства в PropertyGridEditorPart                        
[WebDescription("Set some value")]
//название свойства в PropertyGridEditorPart          
[WebDisplayName("some value")]              
public string Name
{
   get
   {
      return this._name;
   }
   set
   {
      this._name = value;
   }
}

Но что делать, если наш сайт должен поддерживать несколько языков. Значения, передаваемые в атрибуты, прописаны константами, а значит, наши значения в WebDescriptionAttribute и в WebDisplayName нам не подходят. Но и отказываться от столь удобных вещей не стоит. В SharePoint это предусмотрено, там есть атрибут ResourcesAttribute, который получает значения ресурсов, обращаясь к LoadResource(string)

[ResourcesAttribute("PrtName_Name", "PrtName_Category", "PrtName_Description")]
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable]
public string Name
{
  get
  {
     return this._name;
  }
  set
  {
      this._name = value;
  }
}
       
public override string LoadResource(string myID)
{
  ResourceManager rm = new ResourceManager("Properties.MyWebPart_MainResource", System.Reflection.Assembly.GetExecutingAssembly());
  return rm.GetString(myID);
}

Но как быть в Asp.Net? Я предлагаю создать классы-потомки от соответствующих атрибутов, и все встанет на свои законные места. Пример для WebDescription (для WebName аналогично)

public class ResourceWebDescriptionAttribute : WebDescriptionAttribute
{
    string _resourceKey;
    string _classKey;
    public ResourceWebDescriptionAttribute(string classKey, string resourceKey)
    {
        this._resourceKey = resourceKey;
        this._classKey = classKey;
    }
    public override string Description
    {
        get
        {
            return (string)HttpContext.GetGlobalResourceObject(_classKey, _resourceKey);
        }
    }
}

Вы могли увидеть, что это более практично. Если у вас установлен соответствующий Language pack, то и стандартные редакторы свойств будут локализованы. Примеры: ResourceWebDescriptionAttribute.cs В таблице 2 показано, свойства каких типов имеет смысл обозначить как WebBrowsable.

Таблица 2  

Тип свойства

Элемент создаваемый для редактирования свойства

String TextBox
Int, Float, Unit TextBox
Boolean CheckBox
Enum DropDownList
DateTime TextBox

Этого бывает недостаточно. Значит, пришло время познакомиться с EditorPart.

Как работать с EditorPart

Для того чтобы расширить функциональность, нам нужно сделать два шага:

  1. В самой веб части переопределяем public EditorPartCollection CreateEditorParts() - здесь мы создаем наш редактор свойств. Для SPS так же верно (как в 2003) использовать public ToolPart[] GetToolParts().
  2. Создаем свой редактор свойств - потомок EditorPart. В редакторе свойств переопределяем public bool ApplyChanges() - для сохранения наших настроек и public void SyncChanges() - для передачи в редактор настроек.

Примеры: ExchangeRatesCB.cs и ExchangeRatesCBEditor.cs

Усовершенствование внешнего вида. Chrome

Устоявшегося приемлемого перевода Chrome в данном контексте не существует. В langpack FW2 Rus его перевели как «Цвет», а в переводе книги Д. Эспозито "ASP.NET 2.0 Углубленное изучение" назвали «Блеск». Смысл chrome заключается в том, что это html окружающий элементы веб части.

Рассмотрим пример с EditorZone. В SharePoint EditorZone выглядит как на рисунке 3

рисунок 3

Рисунок 3

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

За создание html обертки содержательной части редактора свойств отвечает класс EditorPartChrome, экземпляр которого создается EditorZonePart.CreateEditorPartChrome() Значит все, что нам нужно от EditorZone

public class CollapsibleEditorZone : EditorZone
{
    protected override EditorPartChrome CreateEditorPartChrome()
    {
        return new CollapsibleChrome(this);
    }
}

На CollapsibleChrome ложиться основная функциональность.

public class CollapsibleChrome : EditorPartChrome
{
    EditorPart _editorPart;
    string _imagePlus;
    string _imageMinus;
    public CollapsibleChrome(CollapsibleEditorZone zone) : base(zone)
    {
    }
    public override void RenderEditorPart(HtmlTextWriter writer, EditorPart editorPart)
    {
        if (editorPart == null // editorPart.Page == null)
        throw new ArgumentException("editorPart");
        this._editorPart = editorPart;
        PartChromeType partChromeType = 
        Zone.GetEffectiveChromeType(editorPart);
        Style partChromeStyle = 
        CreateEditorPartChromeStyle(editorPart, partChromeType);
        partChromeStyle.AddAttributesToRender(writer, Zone);
        writer.RenderBeginTag(HtmlTextWriterTag.Fieldset);
        if (partChromeType == PartChromeType.TitleAndBorder //
        partChromeType == PartChromeType.TitleOnly)
        {
        this.RenderTitle(writer, editorPart);
        }
        if (editorPart.ChromeState != PartChromeState.Minimized)
        {
        Style partStyle = this.Zone.PartStyle;
        partStyle.AddAttributesToRender(writer, Zone);
        RenderPartContents(writer, editorPart);
        }
        writer.RenderEndTag();// fieldset
    }
    /// <summary>
    /// Подготавливаем страницу (регистрируем необходимые скрипты)
    /// </summary>
    public override void PerformPreRender()
    {
        base.PerformPreRender();
        //script
        ClientScriptManager cs = Zone.Page.ClientScript;
        if (!cs.IsClientScriptBlockRegistered(this.GetType(), "__togglePart"))
        {
        string js = BuildScript();
        cs.RegisterClientScriptBlock(this.GetType(), 
            "__togglePart", js, false);
        }
    }
    /// <summary>
    /// Достаём необходимы ресурсы (картинки  +/-)
    /// </summary>
    private void InsureResource()
    {
        Type thisType = this.GetType();
        if (string.IsNullOrEmpty(this._imagePlus))
        {
        this._imagePlus =
            Zone.Page.ClientScript.GetWebResourceUrl(thisType,
            "Samples.SimpleParts.Plus.gif");
        }
        if (string.IsNullOrEmpty(this._imageMinus))
        {
        this._imageMinus =
            Zone.Page.ClientScript.GetWebResourceUrl(thisType,
            "Samples.SimpleParts.Minus.gif");
        }
    }
    
    /// <summary>
    /// Рендерим шапку (<legend>)
    /// </summary>
    private void RenderTitle(HtmlTextWriter writer, EditorPart editorPart)
    {
        if (this._editorPart == null // this._editorPart.Page == null)
        return;
        this.InsureResource();
        string displayTitle = editorPart.DisplayTitle;
        if (String.IsNullOrEmpty(displayTitle))
        return;
        TableItemStyle partTitleStyle = this.Zone.PartTitleStyle;
        Style titleTextStyle = new Style();
        titleTextStyle.CopyFrom(partTitleStyle);
        titleTextStyle.AddAttributesToRender(writer, Zone);
        string description = editorPart.Description;
        if (!String.IsNullOrEmpty(description))
        writer.AddAttribute(HtmlTextWriterAttribute.Title, description);
        string accessKey = editorPart.AccessKey;
        if (!string.IsNullOrEmpty(accessKey))
        writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, accessKey);
        writer.RenderBeginTag(HtmlTextWriterTag.Legend);
        //////////////////////////////////
        //img
        string imgId = _editorPart.ClientID + "Img";
        writer.AddAttribute(HtmlTextWriterAttribute.Id, imgId);
        writer.AddAttribute(HtmlTextWriterAttribute.Src, this._imageMinus);
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "__togglePart(\"" 
        + _editorPart.ClientID + "\",\"" + imgId + "\")");
        writer.AddAttribute("onmouseover", "this.style.cursor = \"hand\";");
        writer.AddAttribute("onmouseout", "this.style.cursor = \"\";");
        writer.AddAttribute(HtmlTextWriterAttribute.Alt, "collapse");
        writer.RenderBeginTag(HtmlTextWriterTag.Img);
        writer.RenderEndTag();
        writer.Write(" " +displayTitle);
        writer.RenderEndTag(); // legend
    }
    /// <summary>
    /// Скрипт который будет сворачивать/разворачивать
    /// </summary>
    private string BuildScript()
    {
        this.InsureResource();
        StringBuilder sb = new StringBuilder();
        sb.Append("<script type=text/javascript> "); 
        sb.AppendLine("function __togglePart(divId,imgId) {");
        sb.AppendFormat("   var body = document.getElementById(divId);");
        sb.AppendFormat("   var img = document.getElementById(imgId);");
        sb.AppendLine("   var display = body.style.display;");
        sb.AppendLine("   if (display == \"\") {");
        sb.AppendLine("      body.style.display = \"none\";");
        sb.AppendFormat("      img.src = \"{0}\";\r\n", this._imagePlus);
        sb.AppendLine("   } else {");
        sb.AppendLine("      body.style.display = \"\";");
        sb.AppendFormat("      img.src = \"{0}\";\r\n", this._imageMinus);
        sb.AppendLine("   }");
        sb.AppendLine("}");
        sb.Append("</script>");
        return sb.ToString();
    }
}

Результат показан на рисунке 4:

рисунок 4

Рисунок 4

Таким способом можно «оборачивать» в html все визуальные объекты технологии веб часть. Краткий навигатор создания своего chrome приведен в таблице 3

Таблица 3

Зона

Стандартный chrome

Что создает chrome

Где генерируется html chrome

WebPartZone WebPartChrome WebPartZoneBase.CreateWebPartChrome GetWebPartVerbs, RenderPartContents, RenderWebPart
EditorZone EditorPartChrome EditorZoneBase.CreateEditorPartChrome RenderEditorPart, RenderPartContents
CatalogZone CatalogPartChrome CatalogZoneBase.CreateCatalogPartChrome RenderCatalogPart, RenderCatalogPartContents

Примеры: CollapsibleChrome.cs, CollapsibleEditorZone.cs

Внешние веб части

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

Конфигурация будет храниться в Xml файле вида

<externalWebParts>
  <user name="userName">
    <webParts>
      <webPart path="локальный путь к файлу веб части (.webpart)" 
            id="id веб части (элемента) на странице" 
            webPartZone="id зоны в которую будет добавлена веб часть"
      zoneIndex="индекс веб части в зоне (число)">
      </webPart>
      <webPart path="с:\Webparts\Dasusercontrol.WebPart"
         id="UserWpId" webPartZone="WebPartZone1"  zoneIndex="1">
      </webPart>
    </webParts>
  </user>
</externalWebParts>

Под файлом веб части я подразумеваю файл, получаемый при экспорте веб части.

Рассмотрим задачи:

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

Задача 1

Наследуемся от WebPartManager

public class ExternalWebPartManager : WebPartManager

Переопределяем OnInit(), где считываем настройку и подгружаем соответствующие веб части.

    // добавление веб части на страницу
    WebPart wp = null;
    using (XmlTextReader reader = new XmlTextReader(wpPath))
    {
        string error;
        wp = this.ImportWebPart(reader, out error);
        if (!string.IsNullOrEmpty(error))
        {
            allError.AppendLine(error);
            continue;
        }
    }
    wp.ID = wpId;
    Internals.SetZoneID(wp, wpWebPartZone);
    Internals.SetZoneIndex(wp, wpZoneIndex);
    // выставляем IsShared, для запрета удаления вебчасти
    Internals.SetIsShared(wp, true);
    Internals.AddWebPart(wp); 

Если теперь использовать на странице ExternalWebPartManager, то на страницу добавятся внешние веб части. Файл настройки максимально прост. А файл, определяющий значения по умолчанию свойств веб части просто получить, даже не догадываясь, какие свойства есть у конкретной веб части.

Задача 2

Для создания собственного каталога

public class ExternalCatalog : CatalogPart

мы должны переопределить 2 метода:

Первый:

public override WebPartDescriptionCollection GetAvailableWebPartDescriptions()

здесь мы создаем описания веб частей, для их представления в каталоге. Результат показан на рисунке 5.

рисунок 5

Рисунок 5

Второй:

После выбора веб частей и нажатия на кнопку "Add" вызывается

public override WebPart GetWebPart(WebPartDescription description)

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

Примеры: ExternalCatalog.cs, ExternalWebPartManager.cs

Заключение

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

Ссылки

Официальная документация:


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=8535