Создаем визуальный поиск в IE8 для поиска друзей в Twitter

Источник: habrahabr

С выходом IE8 мы получили одну из самых удобнейших функций в браузере - Визуальные Поисковые Предложения.

Данная статья покажет, как реализовать поиск друзей в твиттере, через специальную Визуальный строку поиска в IE8. По мере набора имени, мы будем видеть аватарку, имя, которое заполнил друг и текущий статус.
Руководство основано на статье Search Provider Extensibility in Internet Explorer из MSDN.


Строим WCF REST сервис.

Создаем новый Empty Web Site, назовем его TwitterFriendsSearch

Добавим новый элемент в проект, WCF Service и назовем его SuggestService.svc.

Это создаст следующие файлы:

  • ISuggestService.cs - контракт Service. Определяет сигнатуру действий сервиса.
  • SuggestService.cs - реализация Service
  • SuggestService.svc -местонахождение сервиса.
  • Web.config -настройки WCF сервиса.

Изменяем определение контракта сервиса(ISuggestService.cs) следующим образом:

[ServiceContract]
public interface ISuggestService
{
 [OperationContract]
 string Search(string friend);
}

Открываем реализацию сервиса (SuggestService.cs). Для начала сделаем простую реализацию, чуть позже изменим это.

public class SuggestService : ISuggestService
{
 public string Search(string friend)
 {
  return "Hello, " + friend;
 }
}

Для обеспечения поддержки REST, добавим обязательный WebGet атрибут над действием сервиса.

[OperationContract]
[WebGet(UriTemplate = "/Search?q={friend}",
    BodyStyle = WebMessageBodyStyle.Bare)]
string Search(string friend);

Меняем настройки WCF сервиса(web.config) для поддержки REST. Расположите узел behaviors и добавьте новую цель поведения, под секцией System.serviceModel:

<system.serviceModel>
 ...
 <behaviors>
  ...
  <endpointBehaviors>
   <behavior name="REST">
    <webHttp/>
   </behavior>
  </endpointBehaviors>
  ...
 </behaviors>
 ...
</system.serviceModel>

В той же секции, располагаем объявление сервиса и местонахождения. Заменяем связывание с wsHttpBinding на webHttpBinding и добавляем поведение местонахождения:

<services>
 <service behaviorConfiguration="SuggestServiceBehavior"
      name="SuggestService">
  <endpoint address=""
       binding="webHttpBinding"
       contract="ISuggestService"
       behaviorConfiguration="REST" />
 </service>
</services>

Запустите проект и перейдите на .svc файл. Далее добавим UriШаблон, который мы объявили в сервисе, с именем друга для поиска:

localhost:50434/TwitterFriendsSearch/SuggestService.svc/search?q=guy

Ответ должен выглядеть как "hello, guy".

Возвращаем результат поиска.

Согласно MSDN странице, ссылку на которую я привел ранее, финальный результат ответа для визуального поиска должен выглядеть так (этот ответ соответствует изображению, которое я прикрепил в начале статьи):

<?xml version="1.0" encoding="utf-8" ?>
<SearchSuggestion>
 <Query>guy</Query>
 <Section>
  <Item>
   <Text>guyhaim</Text>
   <Description>@rohitbhargava tinyurl.com/6m9e4g</Description>
   <Url>twitter.com/home</Url>
   <Image source="http://...normal.jpg"
       height="48" width="48" alt="guyhaim" />
  </Item>
  <Item>
   <Text>Guy Malachi</Text>
   <Description>Yahoo toolbar looks kinda weird.</Description>
   <Url>twitter.com/home</Url>
   <Image source="http://...normal.jpg"
       height="48" width="48" alt="guym" />
  </Item>
  <Item>
   <Text>guy zohar</Text>
   <Description>switch screen</Description>
   <Url>twitter.com/home</Url>
   <Image source="http://...normal.jpg"
       height="48" width="48" alt="guyzo" />
  </Item>
  <Item>
   <Text>guyzarz</Text>
   <Description>@ekampf May it rest in peace, in one piece.</Description>
   <Url>twitter.com/home</Url>
   <Image source="http://...normal.jpg"
       height="48" width="48" alt="guyzarz" />
  </Item>
 </Section>
</SearchSuggestion>

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

// Image.cs
[XmlType]
public class Image
{
 [XmlAttribute(AttributeName="source")]
 public string Source { get; set; }

 [XmlAttribute(AttributeName = "height")]
 public int Height { get; set; }

 [XmlAttribute(AttributeName = "width")]
 public int Width { get; set; }

 [XmlAttribute(AttributeName = "alt")]
 public string Alt { get; set; }
}
// Item.cs
[XmlType]
public class Item
{
 [XmlElement]
 public string Text { get; set; }

 [XmlElement(IsNullable=false)]
 public string Description { get; set; }

 [XmlElement(IsNullable = false)]
 public string Url { get; set; }

 [XmlElement(IsNullable = false)]
 public Image Image { get; set; }
}

// SearchSuggestion.cs
[XmlRoot(Namespace="")]
public class SearchSuggestion
{
 public SearchSuggestion()
 {
  this.Section = new List<Item>();
 }

 [XmlElement]
 public string Query { get; set; }

 [XmlArray]
 public List<Item> Section { get; set; }
}

Изменим контракт сервиса для возвращения SearchSuggestion ответа вместо строки. Так же добавим атрибут XmlSerializerFormat для уверенности, что ответ будет сериализирован в XML.

[ServiceContract]
public interface ISuggestService
{
 [OperationContract]
 [WebGet(UriTemplate = "/Search?q={friend}",
     BodyStyle = WebMessageBodyStyle.Bare)]
 [XmlSerializerFormat]
 SearchSuggestion Search(string friend);
}

Теперь возвращаем в методе Search SearchSuggestion со значениями по умолчанию вместо строки.

public SearchSuggestion Search(string friend)
{
 SearchSuggestion suggestion = new SearchSuggestion
 {
  Query = friend
 };

 suggestion.Section.Add(new Item
 {
  Text = friend,
  Description = "Hello, " + friend,
  Url = "http://blogs.microsoft.co.il/blogs/bursteg",
  Image = new Image
  {
   Source = "http://tinyurl.com/burstegprofileimage",
   Alt = "Guy Burstein",
   Width = 48,
   Height = 48
  }
 });

 return suggestion;
}

Если сейчас запустить проект и перейти снова на ссылку, которую мы использовали ранее, мы должны получить следующий результат:

<?xml version="1.0" encoding="utf-8" ?>
<SearchSuggestion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Query>omer</Query>
 <Section>
  <Item>
   <Text>omer</Text>
   <Description>Hello, omer</Description>
   <Url>blogs.microsoft.co.il/blogs/bursteg</Url>
   <Image source="http://tinyurl.com/burstegprofileimage"
       height="48"
       width="48"
       alt="Guy Burstein" />
  </Item>
 </Section>
</SearchSuggestion>

Добавление визуального поиска в Internet Explorer

Добавляем новый Xml файл в проект и называем его FriendsSuggestion.xml. Этот файл содержит подробную информацию о поисковом плагине для браузера и должен выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
 <ShortName>Friends Search</ShortName>
 <Url type="text/html"
    template="http://localhost:50434/TwitterFriendsSearch/
         SuggestService.svc/search?q={searchTerms}" />
 <Url type="application/x-suggestions+xml"
    template="http://localhost:50434/TwitterFriendsSearch/
         SuggestService.svc/search?q={searchTerms}" />
 <Image height="16"
     width="16"
     type="image/icon">twitter.com/favicon.ico</Image>
</OpenSearchDescription>

Добавляем Default.aspx страницу. Создаем в ней Javascript функцию, которая регистрирует плагин в браузере:

<script type="text/javascript">
function Register() {
 window.external.AddSearchProvider('FriendsSuggestion.xml');
}
</script>

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

<form id="form1" runat="server">
<div>
 <button onclick='Register();'>Click Here</button>
</div>
</form>

Снова запускаем проект, переходим на Default.aspx. Нажимаем на кнопку.

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

Поиск друзей в твиттере

Наконец-то, мы добрались до реализации поиска друзей в твиттере, но данная часть кода может быть изменена под любые ваши нужды.
В реализации сервиса, удаляем значения по умолчанию и делаем HTTP запрос в твиттер для получения списка друзей (замените "bursteg" на свой логин)

string url = string.Format(
  "http://twitter.com/statuses/friends/{0}.xml", "bursteg");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
reader.Close();

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

XDocument document = XDocument.Parse(response, LoadOptions.None);
var query = from e in document.Root.Descendants("user")
      where e.Element("name").Value.Contains(friend) //
         e.Element("screen_name").Value.Contains(friend)
      select new Item
      {

       Text = e.Element("name").Value,
       Image = new Image
             {
              Source = e.Element("profile_image_url").Value,
              Alt = e.Element("screen_name").Value,
              Width = 48,
              Height = 48
             },
       Description = (e.Element("status") == null ? "" :
         HttpUtility.HtmlDecode(e.Element("status").Element("text").Value)),
       Url = (String.IsNullOrEmpty(e.Element("url").Value) ?
           "http://twitter.com/home" :
           e.Element("url").Value)
      };

В заключение, добавим эти элементы в подсказку поиска.

suggestion.Section.AddRange(query);

Полная версия метода Search должна выглядеть так:

public SearchSuggestion Search(string friend)
{
 SearchSuggestion suggestion = new SearchSuggestion();
 suggestion.Query = friend;

 string url = string.Format("http://twitter.com/statuses/friends/{0}.xml", "bursteg");
 HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
 WebResponse response = request.GetResponse();
 StreamReader reader = new StreamReader(response.GetResponseStream());
 string responseString = reader.ReadToEnd();
 reader.Close();

 XDocument document = XDocument.Parse(responseString, LoadOptions.None);
 var query = from e in document.Root.Descendants("user")
       where e.Element("name").Value.Contains(friend) //
          e.Element("screen_name").Value.Contains(friend)
       select new Item
       {

        Text = e.Element("name").Value,
        Image = new Image
        {
         Source = e.Element("profile_image_url").Value,
         Alt = e.Element("screen_name").Value,
         Width = 48,
         Height = 48
        },
        Description = (e.Element("status") == null ? "" :
          HttpUtility.HtmlDecode(e.Element("status").Element("text").Value)),
        Url = (String.IsNullOrEmpty(e.Element("url").Value) ?
            "http://twitter.com/home" :
            e.Element("url").Value)
       };

 suggestion.Section.AddRange(query);
 return suggestion;
}

Теперь, когда ищем друга, мы будем видеть его имя, аватар и текущий статус в предложениях поиска.

Исходный код проекта


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