Вообще, сделать такую же вебчасть раньше не представляло совершенно никаких проблем. Например, на основе TinyMCE. Но сейчас MS реализовал классную фишку с Ribbon'ом, благодаря чему интерфейс редактора даже чем-то напоминает Word. Это выглядит очень клево, и мне нравится, и вообще это стандартная веб-часть, и здорово было бы именно её использовать.
Идея заключается в использовании ControlAdapter. Эта фишка отлично описана в блоге у Waldek Mastykarz. Вкратце, создаем класс адаптера, и затем кидаем короткий xml-файлик my.browser в папку C:\inetpub\<путь к веб-приложению>\App_Browsers.
Именно этот способ применил и я. Мой код класса адаптера выглядел следующим образом:
public class ContentEditorLocalizationAdapter : ControlAdapter { string Localizer(Match m) { return SPUtility.GetLocalizedString( "$Resources:" + m.Groups[2].Value, m.Groups[1].Value, (uint)System.Threading.Thread.CurrentThread.CurrentUICulture.LCID); } protected override void Render(System.Web.UI.HtmlTextWriter writer) { StringBuilder sb = new StringBuilder(); HtmlTextWriter htw = new HtmlTextWriter(new StringWriter(sb)); base.Render(htw); Regex regex = new Regex( "{{\\s*([A-Za-z0-9_\\-]+)\\s*,\\s*([A-Za-z0-9_\\-]+)\\s*}}"); string output = regex.Replace(sb.ToString(), Localizer); writer.Write(output); } }Что делает этот код: после того, как контрол полностью отрендерился, он находит все совпадения {{<имя файла ресурсов>, <имя ресурса>}}, и заменяет эти совпадения на значения соответствующих ресурсов в текущей локали сайта.
При этом всё работает как надо - т.е. "на лету", НО, если пользователь текст меняет - локализация теряется (т.к. локализация в данном случае основана на ресурсных файлах, которые в runtime менять было бы крайне неправильно, на мой взгляд). В принципе, если покопаться в Control adapter'ах, я думаю, можно найти способ полной локализации. Т.е.: переключился на русский, подредактировал русский текст, потом переключился на английский - подредактировал английский. Но, передо мной такой задачи не стояло, хотелось просто, чтобы на презентационном сайте всё выглядело круто, и начальство выделило бы на реализацию полной локализации время.
Теперь про развертывание. Итак, наш продукт поставляется в виде установщика, и совершенно обязательно все эти CEWP задеплоить на сайт клиента.
Веб-части, в том числе Content Editor Web Part, я разворачиваю на целевой сайт с помощью фич. Выглядит это примерно следующим образом:
<Module Name="Pages"> <File Path="Pages\default.aspx" Url="Pages/default.aspx"> <AllUsersWebPart WebPartZoneID="MainZone" WebPartOrder="1"> <![CDATA[ <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2"> <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly> <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName> <Title></Title> <FrameType>None</FrameType> <MissingAssembly>Cannot import this Web Part.</MissingAssembly> <PartImageLarge>/_layouts/images/homepage.gif</PartImageLarge> <ContentLink xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" /> <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"> {{ResourceFileName, Resource1}} </Content> <PartStorage xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" /> </WebPart> ]]> </AllUsersWebPart> </File> </Module>
Этот xml лежит в файле Elements.xml в модуле, содержащем файлик default.aspx с одной объявленной зоной веб-частей, называемой MainZone. Жирным выделен заменяемый нашим адаптером текст. Естественно, в проекте также присутствует SharePoint Mapped Folder "Resources", в который мы положили файлы ResourceFileName.resx, ResourceFileName.en-US.resx и т.п.; и в них забили ресурс с именем Resource1. Как видите, всё довольно просто.
Остается, однако, файлик my.browser, упомянутый выше. Для того, чтобы его положить в искомый App_Browsers, существует несколько ступенчатая, но зато стопроцентная технология, основывающаяся на использовании TimerJob.
Первым делом, я создаю джоб в FeatureReceiver:
public override void FeatureActivated(SPFeatureReceiverProperties properties) { SPSite site = properties.Feature.Parent as SPSite; // Check for an exising instance of the job foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) { if (job.Name == JOB_NAME && job.WebApplication.Name == site.WebApplication.Name) { job.Delete(); } } // Create new job DeployToAppBrowsersJob gsJob = new DeployToAppBrowsersJob(JOB_NAME, site.WebApplication, properties.Definition.DisplayName); gsJob.Title = JOB_TITLE; // Set up the job to run once gsJob.Schedule = new SPOneTimeSchedule(DateTime.Now); gsJob.Update(); }
Класс DeployToAppBrowsersJob, при этом, выглядит следующим образом:
public class DeployToAppBrowsersJob : SPJobDefinition { [Persisted] private string sourcePath; public DeployToAppBrowsersJob() : base() { } public DeployToAppBrowsersJob(string jobName, SPWebApplication webApp, string featureName) : base(jobName, webApp, null, SPJobLockType.Job) { // Detrmine the path to the feature's resource files sourcePath = SPUtility.GetGenericSetupPath("Resources"); } public override void Execute(Guid targetInstanceId) { SPWebApplication webApp = this.Parent as SPWebApplication; foreach (SPUrlZone zone in webApp.IisSettings.Keys) { // The settings of the IIS application to update SPIisSettings oSettings = webApp.IisSettings[zone]; // Determine the destination path string destPath = Path.Combine(oSettings.Path.ToString(), "App_Browsers"); if (!Directory.Exists(destPath)) Directory.CreateDirectory(destPath); File.Move(Path.Combine(sourcePath, "my.browser"), Path.Combine(destPath, "my.browser")); } } }Соответственно, здесь просто происходит вычисление путей, и последующий Move файла.
Весь код проверен и работает как часы.
Удачи!
Комментариев нет:
Отправить комментарий
Внимание! Реклама и прочий спам будут беспощадно удаляться.
Примечание. Отправлять комментарии могут только участники этого блога.