Программное добавление твитта с изображением на C#

Программирование

Tagged Under : , ,

Понадобилось коллеге выполнить в рамках одной из задач добавление сообщения в twitter вместе с картинкой. Примеров не нашёл, но я после непродолжительного поиска нашёл данный исходный код.
Добавил сюда, чтобы потом не искать больше.

Sport Online: следим за результатами матчей с Windows Phone

Проекты

Tagged Under : ,

Ни для кого не секрет, что количество футбольных болельщиков достаточно велико, но не всегда можно присутствовать на матче или в условиях просмотра теле- и видео трансляции. Тем не менее хочется следить за ходом матча или счётом.

Это послужило идеей для создания приложения для смартфонов с windows phone, которое бы позволяло следить за счётом в матче и ходом событий посредством текстовых и аудио трансляций. В ходе развития идеи добавился функционал добавления напоминаний на предстоящие матчи, для которых будут вестись трансляции.
Причём напоминание устанавливается на время корректное именно для времени установленном на смартфоне, т.е. независимо от часового пояса напоминание сработает в нужное время.

Базой для приложения стал портал livetv.ru

В результате реализации описанных идей появилось приложение Sport Online.

скачать Sport Online для Windows Phone

Разработчики vs менеджеры: инструкция для разработчиков

Разное

Итак, ни для кого, наверное, не новость, что бывают случаи, когда к разработчикам появляется ряд вопросов, например:

  • Почему не уложились в сроки?
  • Почему это сделано так, а не как в ТЗ?
  • Чем ты занимался?

и т.д..

Причиной этих вопросов и многих подобных может быть «неправильное» взаимодействие с менеджером или менеджерами, над задачами которых вы работаете.

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

Итак, несколько правил, которые всегда необходимо выполнять:

  • Всегда на проектную задачу должно быть ТЗ, сделанное менеджером проекта. Я думаю смысл вполне ясен, но всё же:
    • данный документ упростит вам разработку,
    • вам не надо будет объяснять тестировщикам, что и как должно работать,
    • вы сможете ответить на вопрос почему вы сделали так, а не иначе

    Проектная задача без ТЗ – самоубийство: в любой момент вам могут сказать, что вы сделали не так как надо и вам нечем будет прикрыться.

  • Если проектная задача находится в процессе активной разработки, а менеджер просит отклониться от ТЗ, то обязательно убедитесь, что данные отклонения попали в ТЗ. Если вы уже сделали ту часть работы, которую затрагивают изменения, то просите менеджера поставить отдельную задачу в системе учёта и постановки задач и выделить на неё дополнительное время.

    Не соблюдение правила грозит тем, что вы возможно затратите времени больше положенного и опять же не сможете в последующем объяснить почему сделано так, а не как в ТЗ.

  • Каждая задача (проектная, суппортная) должна быть поставлена не устно, а в системе постановки и учёта задач. Если такая система не используется, то должно быть письмо по корпоративной почте.

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

  • Любая переписка будь то корпоративная почта или сообщения в клиентах мгновенных сообщения наподобие ICQ, Mai.ru Agent, Yahoo и т.п. должна сохраняться на продолжительное время. Год как минимум.
  • Никогда не делайте задачу, если её должны делать не вы. Например, если вы веб-разработчик, то конечно обладаете базовыми знаниями вёрстки, но в случае, если требуются даже малейшие правки по вёрстке, просите для этих задач верстальщика.

    Это позволит сократить время вашей работы и уложиться в срок.

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

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

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

    Несоблюдение правила грозит тратой времени, ну и вообще не стоит делать за других их работу.

  • Если по казалось бы небольшой части задачи у вас возникло более 5 вопросов, значит вы читали не ТЗ. Просите ТЗ.
  • Никогда не работайте по оценке сделанной другим разработчиков или менеджером. Работать нужно только по своей оценке.

    Несоблюдение правила грозит «растратой» часов. Случается, когда оценивал задачу старший или ведущий разработчик, а реализовывать её отдали простому или младшему разработчику не знакомому с проектом.

  • Не ведитесь на фразы типа «некогда», «потом» и т.д..

Эти правила не являются абсолютными и объективными, выработаны на основе собственного опыта и честно говоря я их почти не применяю.
Правда к команде разработки, в которую вхожу я, по причине не применения подобных правил по одному из проектов претензии предъявили спустя год после сдачи в суппорт.
А ко мне и коллеге по другому проекту сейчас, когда он должен быть закончен, но мы не укладываемся в сроки, потому что работаем по чужой оценке.

WordPress + IIS: 404 ошибки на ссылках с русскими буквами в адресе и при скачивании файлов

Разное

Tagged Under : ,

После переезда блога на хостинг с IIS появились 404 ошибки при попытке открыть адрес, в котором присутствовали русские буквы.
Решение нашлось быстро: IIS wordpress cyrillic permalinks error 404.

Аналогичная проблема также появилась ссылками ведущими на файлы с определёнными расширениями: 7z, nupkg.
С файлами совсем просто: 404 показывается потому что не добавлены соответствующие mime-типы. Это можно сделать в IIS Manager’е в MIME Types либо для всего сервера сразу либо только для нужного сайта.

WCF: метаданные содержат ссылки, которые не могут быть разрешены

Программирование

Tagged Under :

Вчера переносил на новый сервер свои WCF-сервисы и при обновлении прокси-класса столкнулся с ошибкой:

Метаданные содержат ссылки, которые не могут быть разрешены: 'http://example.com/service.svc?wsdl'.
Документ WSDL содержит ссылки, которые невозможно разрешить.
Возникла ошибка при загрузке "http://example.com/service.svc?xsd=xsd0".
Базовое соединение закрыто: Непредвиденная ошибка при приеме.
Не удается прочитать данные из транспортного соединения: Удаленный хост принудительно разорвал существующее подключение.
Удаленный хост принудительно разорвал существующее подключение

или на английском так:

Metadata contains a reference that cannot be resolved: 'http://example.com/service.svc?wsdl'.
The WSDL document contains links that could not be resolved.
There was an error downloading 'http://example.com/service.svc?xsd=xsd0'.
The underlying connection was closed: An unexpected error occurred on a receive.
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.

Проблема в том, что присутствующие в WSDL’е ссылки вида http://example.com/service.svc?xsd=xsd0 не открываются и сервер сбрасывает соединение.
Как выяснилось ошибка состоит в том, что необходимо для процесса под которым запущен Application Pool сайта, в рамках которого работает WCF-сервис установить права на запись в папку C:\Windows\Temp

С данными правами всё начинает корректно работать.

Источник: WCF Add Service Reference gotcha with Windows Server

Xbox 360 и Xbox LIVE: ошибки 8C230002 и 800700E8 в магазине после обновления дашборда (dashboard)

Xbox 360, Xbox LIVE, игры

Tagged Under : ,

Суть проблемы в том, что после обновления дашборда консоли xbox 360 при попытке в магазине открыть описание какой-нибудь игры вместо описания получаем либо ошибку с кодом 8C230002, либо с кодом 800700E8 чаще всего в том случае, если на игру распространяется какая-нибудь скидка. Данная ошибка наблюдается не у всех пользователей.

Поинтересоваться насчёт ошибки и поискать помощи я решил на официальном форуме. Оказалось, что у части пользователей наблюдаются аналогичные проблемы и тех. поддержка не смогла найти решения. Потом один из пользователей Гена Щербаков (GenaShcherbakov) отписался о том, что смог случайным образом исправить данную проблему и на его консоли всё стало работать корректно. Проблема была в пароле на доступ к мессенджеру и лайву.

Его сообщение подтолкнуло меня к мысли, что проблема может заключаться в доступе к контенту (у меня стоял пароль на запуск видео). Тогда я проделал следующие шаги:

  • Перешёл в настройки
  • Выбрал настройку «Семейная» (если вдруг называется по другом, то это квадрат слева от кнопки «Выключить»)
  • Открылось окно «Управление контентом»
  • Выбрал «Выкл»
  • Далее нажал «Сохранить и выйти»

После выполненных действий проблема перестала наблюдаться.

Исходная тема на форуме xbox.com

ASP.NET MVC: простенькая задачка

Программирование

Tagged Under : ,

Итак, у вас есть Action контроллера, в котором содержится следующий фрагмент кода:

ViewBag.Test = "test message";
ViewBag.Alert = "alert message";
ViewData["Alert"] = "alert message 2";

ViewBag.Func = new Func<string, string>(x => {
    ViewBag.Test = x;
    x = ViewBag.Alert;
    ViewBag.Alert = (string)ViewData["Alert"];
    return x;
});

И представление следующего вида:

@ViewBag.Func("x")
@ViewData["Test"]
@ViewBag.Alert

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

HTC Mozart не видит домашнюю Wi-Fi сеть: решение

Разное

Tagged Under : , ,

Недавно приобрёл HTC Mozart. Телефон домашнюю Wi-Fi сеть увидел сразу же как появился на пороге дома и проблем с подключением к ней не наблюдалось.

Всё бы хорошо, но сегодня утром телефон при подключении по Wi-Fi перестал показывать домашнюю сеть среди нескольких доступных. Проанализировав всё, что я делал (на ночь я роутер d-link 815 выключал) подозрение пало на роутер. Проверив настройки Wi-Fi проблем я не обнаружил, погуглив наткнулся на обсуждение следующей темы на форуме поддержки HTC: Проблема с WiFi – не видит некоторые точки доступа.

Учитывая, что в настройках роутера у меня был выбран пункт Enable Auto Channel Selection, я решил чётко установить номер канал и снять автоматический выбор (при включении как раз и происходит выбор канала, видимо был выбран канал «не с тем номером»).

Данные действия помогли и домашняя сеть снова появилась в списке доступных. Оказалось это вроде как особенность аппаратов от HTC.

Orchard CMS: интегрируем Uppod

Программирование

Tagged Under : ,

Итак, появилась необходимость добавить для сайта на базе Orchard CMS, модуль для проигрывания mp3-файлов. Выбранный модуль jPlayer, к сожалению, уронил весь сайт. Не решившись более рисковать решил создать свой модуль на базе плеера Uppod (не факт, конечно, что мой не роняет :) ). С ним я работаю давно, поэтому долго не выбирал.

Процедура создания модуля отличается от других примеров создания модулей на данном сайте тем, что в рамках модуля мы будем активно использовать шорткоды, функционал которых добавляется на сайт путём добавления моего модуля kosfiz.Shortcodes, создание которого описано в записи «Orchard CMS: добавляем поддержку shortcodes«.

Итак, прежде всего необходимо создать модуль, и затем открыть проект с ним. Далее, в папку Models добавляем класс UppodSettingsRecord описывающий настройки плеера, распространяющиеся на весь сайт:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace kosfiz.Uppod.Models
{
    public class UppodSettingsRecord
    {
        public virtual int Id { get; set; }
        public virtual string VideoPlayerStyle { get; set; }
        public virtual int VideoPlayerWidth { get; set; }
        public virtual int VideoPlayerHeight { get; set; }
        public virtual string AudioPlayerStyle { get; set; }
        public virtual int AudioPlayerWidth { get; set; }
        public virtual int AudioPlayerHeight { get; set; }
        public virtual string PhotoPlayerStyle { get; set; }
        public virtual int PhotoPlayerWidth { get; set; }
        public virtual int PhotoPlayerHeight { get; set; }
        public virtual string BackgroundColor { get; set; }
    }
}

Данный класс описывает размеры и стили для каждого из видов плееров (видео, аудио, фото), а также цвет фона.
Потом отражаем свойства описанные в классе в базу посредством класса Migrations

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.Data.Migration;
using System.Data;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;

namespace kosfiz.Uppod
{
    public class Migrations : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("UppodSettingsRecord", table => table.Column("Id", DbType.Int32, column => column.PrimaryKey().Identity())
                .Column("VideoPlayerStyle", DbType.String, column => column.Nullable())
                .Column("VideoPlayerWidth", DbType.Int32, column => column.NotNull().WithDefault(400))
                .Column("VideoPlayerHeight", DbType.Int32, column => column.NotNull().WithDefault(300))
                .Column("AudioPlayerStyle", DbType.String, column => column.Nullable())
                .Column("AudioPlayerWidth", DbType.Int32, column => column.NotNull().WithDefault(300))
                .Column("AudioPlayerHeight", DbType.Int32, column => column.NotNull().WithDefault(90))
                .Column("PhotoPlayerStyle", DbType.String, column => column.Nullable())
                .Column("PhotoPlayerWidth", DbType.Int32, column => column.NotNull().WithDefault(400))
                .Column("PhotoPlayerHeight", DbType.Int32, column => column.NotNull().WithDefault(300))
                .Column("BackgroundColor", DbType.String, column => column.NotNull().WithDefault("ffffff"))
                );

            return 1;
        }
    }
}

Указываем имена полей, типы данных, и значения по умолчанию, а также обязательность. Дальше создаём папку Services и в неё добавляем интерфейс IUppodService и класс, реализующий данный интерфейс UppodService. Данный класс содержит методы обновления и получения настроек плеера, а также метод, который будет вызываться на обработку шорткода uppod. Последний и представляет наибольший интерес:

public static string UppodRender(Dictionary<string, object> atts)
{
    string type = "video";
    if (atts.ContainsKey("type"))
        type = (string)atts["type"];

    string file = string.Empty;
    string playlist = string.Empty;
    if (atts.ContainsKey("files"))
    {
        file = (string)atts["files"];
        if (file.Contains(","))
        {
            playlist = "{ 'playlist' : [" + string.Join(", ", file.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x=> "{" + string.Format(""file":"{0}"", x) + "}").ToList()) + "] }";
            file = string.Empty;
        }
    }

    var settings = GetSettings();

    int width = 0;
    int height = 0;
    string style = string.Empty;
    switch (type)
    {
        case "audio":
            width = settings.AudioPlayerWidth;
            height = settings.AudioPlayerHeight;
            style = settings.AudioPlayerStyle;
            break;
        case "video":
            width = settings.VideoPlayerWidth;
            height = settings.VideoPlayerHeight;
            style = settings.VideoPlayerStyle;
            break;
        case "photo":
            width = settings.PhotoPlayerWidth;
            height = settings.PhotoPlayerHeight;
            style = settings.PhotoPlayerStyle;
            break;
        default:
            break;
    }

    if(atts.ContainsKey("width"))
        width = (int)atts["width"];

   
    if (atts.ContainsKey("height"))
        height = (int)atts["height"];

    string Poster = string.Empty;
    if (atts.ContainsKey("poster"))
        Poster = (string)atts["poster"];

    string Comment = string.Empty;
    if (atts.ContainsKey("comment"))
        Comment = (string)atts["comment"];

    Random rnd = new Random();
    string playerId = "uplayer_" + rnd.Next(0, 10000);

    StringBuilder flashvars = new StringBuilder();
    flashvars.Append("{");
    flashvars.AppendFormat(""m":"{0}","
    + ""uid":"{1}","
    + ""{2}": "{3}""
    + "{4}"
    + "{5}"
    + "{6}", type, playerId, string.IsNullOrEmpty(file) ? "pl" : "file", string.IsNullOrEmpty(file) ? playlist.Replace(""", "'") : file,
    string.IsNullOrEmpty(style) ? "" : ", "st" : "" + style + """, string.IsNullOrEmpty(Poster) ? "" : string.Format(", "poster" : "{0}"", Poster),
    string.IsNullOrEmpty(Comment) ? "" : string.Format(", "comment" : "{0}"", Comment));
    flashvars.Append("}");

    StringBuilder flashparams = new StringBuilder();
    flashparams.Append("{");
    flashparams.AppendFormat("id:"{0}", "
    + "bgcolor:"#{1}", allowFullScreen:"true", allowScriptAccess:"always"", playerId, settings.BackgroundColor);
    flashparams.Append("}");

    StringBuilder htmlparams = new StringBuilder();
    htmlparams.Append("{");
    htmlparams.AppendFormat("m: "{0}", comment: "{1}", uid: "{2}", {3} : "{4}"", type, string.IsNullOrEmpty(Comment) ? "" : Comment,
        playerId, string.IsNullOrEmpty(file) ? "pl" : "file", string.IsNullOrEmpty(file) ? playlist.Replace(""", "'
") : file);
    htmlparams.Append("
}");

    string div = string.Format("
<div id="{0}" style="width:{1}px; height:{2}px;"></div>", playerId, width, height);
    string script = string.Format("
<script type="text/javascript">init("{0}", {1}, {2}, {3}, {4}, {5})</script>", playerId, flashvars, flashparams, htmlparams, width, height);

    return string.Format("
{0}<br/>{1}", div, script);
}

Данный метод добавляет в страницу вместо шорткода div и скрипт вызывающий инициализацию плеера. Сама функция инициализации (и другие) содержится в скрипте и выглядит следующим образом:

function initUppodFlashPlayer(playerId, flashvars, flashparams, width, height) {
    swfobject.embedSWF("/Modules/kosfiz.Uppod/Scripts/uppod.swf", playerId, width, height, "9.0.115", false, flashvars, flashparams);
}

function initHtml5Player(htmlparams) {
    var v = document.createElement('video');
    if (v.canPlayType('video/mp4')) {
        this.vplayer = new Uppod(htmlparams);
    }
    else { }

}
function init(playerId, flashvars, flashparams, htmlparams, width, height) {
    var playerVersion = swfobject.getFlashPlayerVersion();

    if (playerVersion.major >= 9)
        initUppodFlashPlayer(playerId, flashvars, flashparams, width, height);
    else
        initUppodHtml5Player(htmlparams);
}

Другие файлы необходимые для работы плеера добавляем в папку Scripts: uppod.js (html5-версия), uppod.swf (плеер), swfobject (вдруг ещё не добавлен), uppod_init.js (содержимое приведено выше).

Теперь надо добавить скрипты в код страницы и добавить шорткод uppod и метод для его обработки в словарь шорткодов. Для этого добавляем папку Handlers и в него класс UppodHandler:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.ContentManagement.Handlers;
using kosfiz.Uppod.Models;
using Orchard.Data;
using Orchard;
using Orchard.UI.Resources;
using Orchard.Caching;
using kosfiz.UppodServices;
using kosfiz.Shortcodes.Models;

namespace kosfiz.Uppod.Handlers
{
    public class UppodHandler: ContentHandler
    {
        readonly IWorkContextAccessor _workContextAccessor;
        public UppodHandler(IWorkContextAccessor workContextAccessor, IRepository<UppodSettingsRecord> repository, ICacheManager cacheManager, ISignals signals)
        {
            try
            {
                _workContextAccessor = workContextAccessor;
                var resourceManager = _workContextAccessor.GetContext().Resolve<IResourceManager>();
                var links = resourceManager.GetRegisteredLinks();
                bool uppod = false;
                bool swfobject = false;
                foreach (var link in links)
                {
                    if (link.Href.ToLower().Contains("uppod"))
                        uppod = true;
                    if (link.Href.ToLower().Contains("swfobject"))
                        swfobject = true;
                }

                if (!swfobject)
                    resourceManager.RegisterHeadScript("<script type="text/javascript" src="/Modules/kosfiz.Uppod/Scripts/swfobject-2.2.min.js"></script>");
                if (!uppod)
                {
                    resourceManager.RegisterHeadScript("<script type="text/javascript" src="/Modules/kosfiz.Uppod/Scripts/uppod.js"></script>");
                    resourceManager.RegisterHeadScript("<script type="text/javascript" src="/Modules/kosfiz.Uppod/Scripts/uppod_init.js"></script>");
                }
            }
            finally
            {
                ShortCodeService.AddShortCode("uppod", UppodService.UppodRender); //добавляем шорткод в список обрабатываемых, указывая в какой метод передавать параметры
                UppodService uppodService = new UppodService(repository, cacheManager, signals);
            }
        }
    }
}

На этом программирование основного функционала можно считать законченным, осталось лишь добавить код для внесения настроек модуля в админку.
Добавляем AdminMenu в корень проекта

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.UI.Navigation;
using Orchard.Localization;

namespace kosfiz.Uppod
{
    public class AdminMenu : INavigationProvider
    {
        public Localizer T { get; set; }

        public AdminMenu()
        {
            T = NullLocalizer.Instance;
        }

        public void GetNavigation(NavigationBuilder builder)
        {
            builder.Add(T("Uppod"), "49", menu => menu.Add(T("Uppod"), "0", item => item.Action("Index", "Admin", new { area = "kosfiz.Uppod" })));
        }

        public string MenuName
        {
            get { return "admin"; }
        }
    }
}

Осталось добавить контроллер и представление, соответственно:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Orchard.UI.Admin;
using kosfiz.Uppod.ViewModels;
using kosfiz.Uppod.Services;
using Orchard;
using Orchard.Localization;

namespace kosfiz.Uppod.Controllers
{
    [ValidateInput(false), Admin]
    public class AdminController: Controller
    {
        private readonly IUppodService _uppodService;
       
        public IOrchardServices Services { get; set; }
        public Localizer T { get; set; }

        public AdminController(IOrchardServices services, IUppodService uppodService)
        {
            Services = services;
            _uppodService = uppodService;
        }

        [HttpGet]
        public ActionResult Index()
        {
            var m = _uppodService.Get();
            UppodSettingsViewModel model = new UppodSettingsViewModel { AudioPlayerHeight = m.AudioPlayerHeight, AudioPlayerStyle = m.AudioPlayerStyle, AudioPlayerWidth = m.AudioPlayerWidth, BackgroundColor = m.BackgroundColor, VideoPlayerHeight = m.VideoPlayerHeight, VideoPlayerStyle = m.VideoPlayerStyle, VideoPlayerWidth = m.VideoPlayerWidth, PhotoPlayerHeight = m.PhotoPlayerHeight, PhotoPlayerStyle = m.PhotoPlayerStyle, PhotoPlayerWidth = m.PhotoPlayerWidth };
            return View(model);
        }

        [HttpPost]
        public ActionResult Index(UppodSettingsViewModel model)
        {
            if (ModelState.IsValid)
            {
                _uppodService.Set(model.VideoPlayerStyle, model.VideoPlayerWidth, model.VideoPlayerHeight, model.AudioPlayerStyle, model.AudioPlayerWidth, model.AudioPlayerHeight, model.PhotoPlayerStyle, model.PhotoPlayerWidth, model.PhotoPlayerHeight, model.BackgroundColor);
                return RedirectToAction("Index");
            }
           
            return View();
        }
    }
}
@model kosfiz.Uppod.ViewModels.UppodSettingsViewModel

<h1>@Html.TitleForPage(@T("Manage Uppod settings").ToString())</h1>
@using (Html.BeginFormAntiForgeryPost())
{
    <fieldset>
    <table>
        <tr>
            <td>@T("Background color"):</td>
            <td>
                @Html.TextBoxFor(model=>model.BackgroundColor)
            </td>
        </tr>
        <tr>
            <td colspan="3">
                <h2>@T("Video")</h2>
            </td>
        </tr>
        <tr>
            <td>@T("Style"):</td>
            <td>
                @Html.TextBoxFor(model=>model.VideoPlayerStyle)
            </td>
        </tr>
        <tr>
            <td>@T("Width"):</td>
            <td>
                @Html.TextBoxFor(model=>model.VideoPlayerWidth)
            </td>
        </tr>
        <tr>
            <td>@T("Height"):</td>
            <td>
                @Html.TextBoxFor(model=>model.VideoPlayerHeight)
            </td>
        </tr>
        <tr>
            <td colspan="3">
                <h2>@T("Audio")</h2>
            </td>
        </tr>
        <tr>
            <td>@T("Style"):</td>
            <td>
                @Html.TextBoxFor(model=>model.AudioPlayerStyle)
            </td>
        </tr>
        <tr>
            <td>@T("Width"):</td>
            <td>
                @Html.TextBoxFor(model=>model.AudioPlayerWidth)
            </td>
        </tr>
        <tr>
            <td>@T("Height"):</td>
            <td>
                @Html.TextBoxFor(model=>model.AudioPlayerHeight)
            </td>
        </tr>
        <tr>
            <td colspan="3">
                <h2>@T("Photo")</h2>
            </td>
        </tr>
        <tr>
            <td>@T("Style"):</td>
            <td>
                @Html.TextBoxFor(model=>model.PhotoPlayerStyle)
            </td>
        </tr>
        <tr>
            <td>@T("Width"):</td>
            <td>
                @Html.TextBoxFor(model=>model.PhotoPlayerWidth)
            </td>
        </tr>
        <tr>
            <td>@T("Height"):</td>
            <td>
                @Html.TextBoxFor(model=>model.PhotoPlayerHeight)
            </td>
        </tr>

    </table>
    <input type="submit" value="@T("Save")" title="@T("Save")" />
    </fieldset>
}

Остаётся только собрать модуль.

Ссылка на модуль

Оба модуля успешно используются, к примеру, здесь: http://school286.ru/odod-voenno-patrioticheskoe

P.S.: CodeColorer «любезно» во вставках кода подтёр кое-что в тех местах, где идёт работа со строками, так что лучше смотреть код в модуле.

Orchard CMS: добавляем поддержку shortcodes

Программирование

Tagged Under : , ,

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

Например, плагин подсветки CodeColorer, который использую я. Аналогичный функционал понадобился бы при реализации спойлер-блоков, как, например, в статье на сайте kanobu.ru. Применений можно найти множество.

Данной возможности в Orchard CMS по умолчанию не наблюдается: там всё по заранее настроенному порядку, т.е. один тип контента нельзя вставить внутрь другого, нельзя добавить несколько одинаковых типов контента к статье (допустим несколько галерей). Это, естественно, ограничивает разработчиков и, как следствие, конечных пользователей.

Итак, задача, которую нужно прежде всего решить, это программно переопределить Parts.Common.Body.cshtml таким образом, чтобы в ней обрабатывались шорткоды.

Пошагово процесс выглядит следующим образом:

  • Добавить класс, описывающий шорткод
  • Реализовать класс, через который можно будет добавлять шорткоды и методы их обрабатывающие. Реализовать в рамках класса простейший парсер шорткодов и отрисовку
  • Переопределить Parts.Common.Body.cshtml

Создадим модуль и откроем его проект в Visual Studio или другой среде разработки. В папку Modules добавляем класс ShortCodeItem со следующим содержимым:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace kosfiz.Shortcodes.Models
{
    public class ShortCodeItem
    {
        private string codeName = string.Empty;
        public string CodeName
        {
            get
            {
                return codeName;
            }
            set
            {
                codeName = value;
            }
        }

        private int startIndex;
        public int StartIndex
        {
            get
            {
                return startIndex;
            }
            set
            {
                startIndex = value;
            }
        }

        private int endIndex;
        public int EndIndex
        {
            get
            {
                return endIndex;
            }
            set
            {
                endIndex = value;
            }
        }

        public string TargetText
        {
            get;
            set;
        }

        public string SourceText
        {
            get;
            set;
        }
    }
}

На следующем шаге добавляем ShortCodeService, который и будет выполнять основные функции.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;

namespace kosfiz.Shortcodes.Models
{
    public class ShortCodeService
    {
        //здесь будем хранить все шорткоды и методы, их обрабатывающие
        static Dictionary<string, Func<Dictionary<string, object>, string>> Methods = new Dictionary<string, Func<Dictionary<string, object>, string>>();

        //метод добавления шорткода
        public static void AddShortCode(string ShortCodeName, Func<Dictionary<string, object>, string> method)
        {
            if (!Methods.ContainsKey(ShortCodeName)) //если шорткод уже есть, то игнорируем его
                Methods.Add(ShortCodeName, method);
        }

        //выполняем метод соответсвующий имени шорткода и передаём ему параметры, сторонний метод должен принимать на выход словарь строка = объект
        private static string DoMethod(string ShortCodeName, Dictionary<string, object> atts)
        {
            return Methods[ShortCodeName].Invoke(atts);
        }

        //из текста выбирает шорткоды
        private static List<ShortCodeItem> GetShortCodes(string Text)
        {
            List<ShortCodeItem> codes = new List<ShortCodeItem>();

            foreach (var item in Methods)
            {
                try
                {
                    string startTag = string.Format("[{0}", item.Key);
                    string endTag = string.Format("[/{0}]", item.Key);

                    int startIndex = Text.IndexOf(startTag);
                    while (startIndex != -1)
                    {
                        int endIndex = Text.IndexOf(endTag, startIndex);
                        if (endIndex != -1)
                        {
                            int paramsEndIndex = Text.IndexOf("]", startIndex);
                            if (paramsEndIndex != -1)
                            {
                                string source = Text.Substring(startIndex + startTag.Length, paramsEndIndex - startIndex - startTag.Length);
                                Dictionary<string, object> atts = GetValues(source, paramsEndIndex + 1, endIndex - paramsEndIndex - 1, Text);
                                codes.Add(new ShortCodeItem { CodeName = item.Key, StartIndex = startIndex, EndIndex = endIndex, TargetText = DoMethod(item.Key, atts), SourceText = Text.Substring(startIndex, endIndex + endTag.Length - startIndex) });
                            }
                        }
                        startIndex = Text.IndexOf(startTag, startIndex + 1);
                    }
                }
                catch (Exception) { }
            }

            return codes;
        }

        //формируем словарь параметров шорткода
        private static Dictionary<string, object> GetValues(string source, int InnerStart, int InnerEnd, string InnerText)
        {
            Dictionary<string, object> values = new Dictionary<string, object>();

            int paramNameIndex = source.IndexOf("=");
            int lastParamValueIndex = 0;
            while (paramNameIndex != -1)
            {
                string attrName = source.Substring(0, paramNameIndex);
                string attrValue = string.Empty;
                if (source[paramNameIndex + 1] != '"')
                {
                    lastParamValueIndex = source.IndexOf(" ", paramNameIndex + 1);
                    if (lastParamValueIndex == -1)
                        lastParamValueIndex = source.Length - 1;
                    attrValue = source.Substring(paramNameIndex + 1, lastParamValueIndex - paramNameIndex);
                }
                else
                {
                    lastParamValueIndex = source.IndexOf("\"", paramNameIndex + 2);
                    if (lastParamValueIndex == -1)
                        lastParamValueIndex = source.Length - 1;
                    attrValue = source.Substring(paramNameIndex + 2, lastParamValueIndex - paramNameIndex - 2);
                }

                values.Add(attrName.Trim(), attrValue.Trim());
                source = source.Remove(0, lastParamValueIndex);
                paramNameIndex = source.IndexOf("=");
            }
            //InnerHtml предопределённый параметр, содержит содержимое тегов шорткода [shortcodeName]InnerHtml[/shortcodeName]
            values.Add("InnerHtml", InnerText.Substring(InnerStart, InnerEnd).Trim());
            return values;
        }

        //заменяем теги шорткодов на html сгенерированный модулями
        public static MvcHtmlString Render(string Text)
        {
            List<ShortCodeItem> codes = GetShortCodes(Text);
            foreach (var item in codes)
                Text = Text.Replace(item.SourceText, item.TargetText);
            return MvcHtmlString.Create(Text);
        }
    }
}

Собственно, парсер обрабатывает шорткод теги вида:
[shortcodeName attr1=value attr2="value with whitespace or =" attr3=value2]different text[/shortcodeName]

Т.е. необходим закрывающий шорткод тег, значения с пробелом или = заключается в двойные кавычки. Содержимое между открывающим и закрывающим шорткодами передаётся во внешний метод по ключу InnerHtml.

Осталось переопределить Parts.Common.Body.cshtml. Для этого добавляем в папку Views проекта копию оригинального файла и меняем его содержимое на следующее:

@using kosfiz.Shortcodes.Models;
@{
    var body = ShortCodeService.Render(Model.Html.ToString());
}
@body

Далее добавляем в папку Models класс ShortCodeShapeProvider

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.DisplayManagement.Descriptors;
using Orchard;
using Orchard.ContentManagement;

namespace kosfiz.Shortcodes.Models
{
    public class ShortCodeShapeProvider : IShapeTableProvider
    {
        private readonly IWorkContextAccessor _workContextAccessor;
        public ShortCodeShapeProvider(IWorkContextAccessor workContextAccessor)
        {
            _workContextAccessor = workContextAccessor;
        }

        public void Discover(ShapeTableBuilder builder)
        {
            builder.Describe("Parts_Common_Body").OnDisplaying(displaying =>
            {
                ContentItem item = displaying.Shape.ContentItem;
                if (displaying.ShapeMetadata.DisplayType == "Detail")
                {
                    displaying.ShapeMetadata.Alternates.Add("Parts_Common_Body");
                }
            });
        }
    }
}

Вот собственно и всё. Пример создания модуля использующего шорткоды описан в статье «Ссылка на модуль