Встраиваем MetaWeblog API на свой сайт

    • ASP.NET
    • .NET
    • WCF
    • Windows Live
    • MVC
    • outcoldman.ru
    • SOA
    • MetaWeblog
    • Windows Live Writer
    • XML-RPC
  • modified:
  • reading: 6 minutes

Пару недель назад мне попалась на глаза небольшая статья Feels like a new day – implementing the MetaWeblog API. В тот же момент у меня и родилась мысль, что нужно сделать поддержку MetaWeblog API, а следовательно и Windows Live Writer, у себя на сайте. В прошедшие выходные нашел немного свободного времени и начал исследование ссылок, которые были в статье. Обе ссылки, в которых были примеры реализации, основывались на XML-RPC.NET, мне же хотелось все реализовать на WCF сервисах. В пару кликов нашел статью XML-RPC with WCF (Updated), в которой был небольшой пример реализации на WCF сервисах и реализована библиотека, в которой был XML-RPC Behavior с самодельным сериализатором, поддерживающим XML-RPC. В общем, материал был найден и я приступил к реализации.

Как это часто бывает: библиотека, что я нашел была хорошая, но не идеальная, пример работал, но не на 100%. Начал разбираться по пунктам. Ознакомился так же со спецификацией на MetaWeblog API, который предоставляет Windows Live Writer: MetaWeblog API Reference. Сразу вспомнились основные принципы разработки SOA, если один раз уже выложил плохую реализацию (или какую-нибудь), то мучайся и поддерживай ее до конца, так же и тут встречаются такие вот параметры:

syntax_sample

Дальше я начал описывать все структуры, которые используются в этом API, только сделал я их классами (не понял, почему во всех примерах так все норовят использовать именно структуры). Так же я не стал уподобляется примерам и сделал хранение всех параметров через свойства с нормальными именами:

[DataContract]public class Post
{    [DataMember(Name = "dateCreated")]
    public DateTime DateCreated { get; set; }
     [DataMember(Name = "date_created_gmt")]
    public DateTime DateCreatedGmt { get; set; }
     // ....
}

Кстати, обратите внимание на второе свойство DateCreatedGmt, его в примерах реализаций нет, его я обнаружил через Fiddler:

fiddler2

Нашел, я его, кстати, не случайно, а потому что библиотека, которую я взял падала при попытке распарсить запрос. Она просто не была заточена на то, что в запросе могут быть параметры, которые не ожидаются. Так же у меня она падала и после добавления этого параметра, так как она просто не понимала, что нужно делать с попадающимся текстом “\n   ”, тут мне все таки пришлось добавить небольшой фикс в класс XmlRpcDataContractSerializationHelper (Ln 34):

while (reader.NodeType != XmlNodeType.EndElement)
{    if (reader.NodeType == XmlNodeType.Text)
    {
        reader.ReadString();        continue;
    }
     //....
}

Но тут проблемы не закончились, обратите внимание на формат передаваемой даты (и это iso8601!), конечно же сериализатор не смог ее распарсить, потому пришлось дописать еще немного кода в том же классе:

public class XmlRpcDataContractSerializationHelper
{    private const string DefaultDateTimeFormat = "yyyyMMddTHH:mm:ss";
    //...
    public static object Deserialize(XmlDictionaryReader reader, Type targetType)
    {        //...            
            // Ln 135
            case XmlRpcProtocol.DateTime:
                string dateTime = reader.ReadElementContentAsString();
                returnValue = Parser.ToDateTime(dateTime, DefaultDateTimeFormat);                break;
        //...
    }
     public static void Serialize(XmlDictionaryWriter writer, object value)
    {        // ...
            // Ln 260
            else if (valueType == typeof (DateTime))
            {
                writer.WriteStartElement(XmlRpcProtocol.DateTime);                writer.WriteValue(((DateTime)value).ToString(DefaultDateTimeFormat));
                writer.WriteEndElement();
            }        // ...
    }
}

Правил я именно для случая с Windows Live Writer, если же хочется использовать эту библиотеку не только там, то лучше бы формат даты вынести в настраиваемые параметры. Используемый метод Parser.ToDateTime очень прост:

public static DateTime ToDateTime(object obj, string format)
{    IFormatProvider culture = new CultureInfo("en-US");
    return DateTime.ParseExact(obj.ToString(), format, culture);
}

В плане реализации самого сервиса, то там все прозрачно, нужно лишь взять один из примеров и реализовать каждый отдельный метод. Мне очень повезло, что MetaWeblog API предоставляет возможность возвращаться ссылки на несколько блогов для пользователя, потому у хорошо вписалась концепция, что на моем сайте есть, вроде как, два блога: русский и английский. Эти данные возвращаются в методе сервиса blogger.getUsersBlogs. Убедитесь только, что правильно указаны Url на блоги, так как в моем случае я долго не мог понять, почему у меня Windows Live Writer не мог загрузить тему с сайта, оказалось все просто, я указал ссылку https://www.outcoldman.com, хотя должен был для каждого языка указать четкую ссылку на страницу, где записи будут видны, для русского языка – это, например, https://www.outcoldman.com/ru/blog/index.

В примере к статье  XML-RPC with WCF (Updated) сервис выставляется наружу при помощи настройки в runtime, мне же нужно было правильно все настроить через web.config:

<system.serviceModel>
    <bindings>
        <webHttpBinding>
            <binding name="Custom" openTimeout="01:00:00" sendTimeout="01:00:00" receiveTimeout="01:00:00" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
                <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647"/>
            </binding>
        </webHttpBinding>
    </bindings>
    <extensions>
        <behaviorExtensions >
            <add name="xmlRpc" type="Microsoft.Samples.XmlRpc.XmlRpcEndpointBehaviorSection, Microsoft.Samples.XmlRpc"  />
        </behaviorExtensions>
    </extensions>
    <services>
        <service name="PersonalWeb.Metablog.MetaWeblog"  >
            <endpoint binding="webHttpBinding" bindingConfiguration="Custom" contract="PersonalWeb.Metablog.IMetaWeblog" 
                      behaviorConfiguration="XmlRpcEndpoint"  />
        </service>
    </services>
    <behaviors>
        <endpointBehaviors>
            <behavior name="XmlRpcEndpoint"  >
                <xmlRpc />
            </behavior>
        </endpointBehaviors>
    </behaviors>
     <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
 </system.serviceModel>

Конфигурацию для webHttpBinding пришлось делать после того, как осознал что картинки из Writer не передаются на сервер, тогда увеличил там все параметры сразу же, чтобы потом не натыкаться на другие проблемы. Последняя настройка serviceHostingEnvironment связана с тем, что уже после деплоя всего на рабочий сайт получил ошибку:

This collection already contains an address with scheme http.  There can be at most one address per scheme in this collection. If your service is being hosted in IIS you can fix the problem by setting 'system.serviceModel/serviceHostingEnvironment/multipleSiteBindingsEnabled' to true or specifying 'system.serviceModel/serviceHostingEnvironment/baseAddressPrefixFilters'.
Parameter name: item

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

Ну и не забываем сделать файлик на сайте MetaWeblogService.svc:

<%@ ServiceHost Language="C#" Debug="true" Service="PersonalWeb.Metablog.MetaWeblog" %>

Перед тем как деплоить, решал еще несколько проблем. Первая – это не было возможности добавлять свои теги из Writer, опять нагуглил решение – вместо MetaWeblog API в качестве сервиса при настройке блога в Writer нужно указать Community Server и ту же ссылку на свою реализацию MetaWeblog API сервиса. Безумно простое решение, и самое главное оно работает. Вторая проблема – это нет поддержки “Разделителя записи”, чтобы была Abstraction и Body записи (так сказать, что под cut’ом). Эту проблему решил при помощи “<hr />”, ее то вставить в блог можно, а на сервере уже нахожу это место и разрываю спокойно запись (вряд ли мне когда-нибудь реально понадобиться “<hr />”). Еще была странная проблема с датами, возвращаю все в “правильном” формате, который ожидает Writer для существующих статей, и даты даже отображаются в списке предыдущих статей, но никак не хочет их Writer подставлять для редактируемой статьи. Решил просто забить на даты, и выставлять их на сервере при создании статьи. И последняя, самая неприятная для меня, проблема была со стилями сайта, мой сайт раньше не поддерживал IE6/7, а вот Writer рендерит вроде все на IE7 движке, из-за чего тема нормально в нем не отображалась, пришлось потратить небольшое количество времени для переверстки сайта. Вроде получилось, даже убрал специальный хак для Opera. Да, если у вас разъедется разметка – то просто прорефрешьте страницу. Подмигивающая рожица

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

P.S. Если вы прочли эту статью, значит у меня действительно все работает. Улыбка

See Also