среда, 22 июня 2016 г.

Получение значения вложенного XElement-а.

[Getting the value of a nested XElement]

Допустим у нас есть следующий документ:

XDocument xDoc = XDocument.Parse(@"<Data> <X> <Y> <Z>Здесь!</Z> </Y> </X> </Data>");

Получение значения XElementa  „Z“ достаточно тривиальная задача:

String value = xDoc.Root.Element("X").Element("Y").Element("Z").Value;

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

Что бы получить значение «в один вызов», можно воспользоваться методами расширениями:

public static String xValueOrNull (this XElement xEl) {
if (xEl == null) return null;
else return xEl.Value;
}
public static XElement xElementOrNull(this XElement root, String elemName) {
if (root == null) return null;
return root.Element(elemName);
}  
Тогда вызов будет примерно таким:

String val = xDoc.Root.xElementOrNull("X").xElementOrNull("Y").xElementOrNull("Z").xValueOrNull();

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

public static String xFuncValueOrNull(this XElement xEl, String elemName, Func<XElement, String> func) {
if (xEl == null) return null;
XElement subEl = xEl.Element(elemName);
if (subEl == null) return null;
return func(subEl);
}

И вызов:

String val = xDoc.Root.xFuncValueOrNull("X", x => x.xFuncValueOrNull("Y", y => y.xFuncValueOrNull("Z", z => x.Value)));

вторник, 16 февраля 2016 г.

Использование XING API из c# (.NET)

[Using the XING API in c # (.NET)]

Далее представлен пример работы с XING-API из десктопного приложения (WinForms).
Документация по XING-API:https://dev.xing.com/docs/resources.

Итак, для начала использования API необходимо прежде всего получить ключ и секретное слово на следующей страничке: https://dev.xing.com/applications.

Использование API предполагает придерживания специального протокола OAuth (https://dev.xing.com/docs/authentication). Как это делается можно увидеть в коде. Особенностью десктопного приложения является то, что верификация пользователя выполняется “вручную” - пользователь должен передать в приложение специальный код, который отображается после его авторизации в браузере. В веб-приложении этот код можно вычитать из возвращаемых параметров запроса программно.

Ресурсы, которые я использовал:

  • .Net SDK for XING API

https://netforxing.codeplex.com/ (странная реализация, но подсмотрел как использовать DotNetOpenAuth)

  • Wie man die XING-API nutzt (на немецком, описан процесс использования OAuth в XING)

http://www.heise.de/ix/artikel/Sozial-integriert-1892413.html

  • API Explorer für die XING REST-API (программа для тестирования API)

https://www.cybertribe.de/druid/de/ApiExplorer


В приложении понадобятся следующие пакеты библиотек:

DotNetOpenAuth OAuth 1.0(a) Consumer:


и RestSharp:

Диалог ввода использовал из сборки "Microsoft.VisualBasic".

Интерфейс приложения:

По нажатию на кнопку "Login" (button1_Click) выводится URL, который необходимо ввести в браузере для авторизации пользователя и получения кода верификации. Этот код необходимо ввести в последующем диалоге ввода. После логина можно вызывать методы API, например получение информации о пользователе - кнопка "Get Data" (button2_Click).


Итак, код:

public partial class Form1 : Form
{
    public Form1() {
        InitializeComponent();
    }

    String consumerKey = "<your key>";
    String consumerSecret = "<your secret>";

    String access_token = null;
    String access_token_secret = null;

    String verifyPIN = null;

    private void button1_Click(object sender, EventArgs e) {
           
        A();
        B();

        verifyPIN = Microsoft.VisualBasic.Interaction.InputBox("PIN: ",
                    "Input Data",
                    "", 0, 0);

        C();
    }

    private void button2_Click(object sender, EventArgs e) {

        String userID = Microsoft.VisualBasic.Interaction.InputBox("User ID",
                    "Input Data",
                    "me", 0, 0);

        D(userID);
    }

    // -----------------
    // -----------------

    // Get Authorization Token
    void A() {
        RestClient client = new RestClient("https://api.xing.com");

        client.Authenticator = OAuth1Authenticator.ForRequestToken(
                                                        consumerKey,
                                                        consumerSecret,
                                                        "oob");

        RestRequest request = new RestRequest("v1/request_token", Method.POST);
        RestResponse response = (RestResponse)client.Execute(request);

        var qs = HttpUtility.ParseQueryString(response.Content);
        access_token = qs["oauth_token"];
        access_token_secret = qs["oauth_token_secret"];
    }

    // Use Token to get the verifier.
    void B() {
        RestClient client = new RestClient("https://api.xing.com");

        RestRequest request = new RestRequest("/v1/authorize");
        request.AddParameter("oauth_token", access_token);
        var url = client.BuildUri(request).ToString();

        // use this url to get PIN Code
        MessageBox.Show(url);
    }

    // Use Access Token and Verifier to get the last Token needed
    void C() {
        RestClient client = new RestClient("https://api.xing.com");

        client.Authenticator = OAuth1Authenticator.ForAccessToken(
                                                        consumerKey,
                                                        consumerSecret,
                                                        access_token,
                                                        access_token_secret,
                                                        verifyPIN);

        RestRequest request = new RestRequest("v1/access_token", Method.POST);
        RestResponse response = (RestResponse)client.Execute(request);

        // Change tokens to the correct Access Tokens
        // now we have all Tokens in our TokenManager
        var qs = HttpUtility.ParseQueryString(response.Content);
        access_token = qs["oauth_token"];
        access_token_secret = qs["oauth_token_secret"];
    }

    // Get Data
    void D(String userID) {
        RestClient client = new RestClient("https://api.xing.com");

        client.Authenticator = OAuth1Authenticator.ForProtectedResource(
                                                        consumerKey,
                                                        consumerSecret,
                                                        access_token,
                                                        access_token_secret);

        RestRequest request = new RestRequest("/v1/users/" + userID);
        RestResponse response = (RestResponse)client.Execute(request);


        //Verify Result
        //Assert.NotNull(response);
        //Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        MessageBox.Show(response.Content);
    }
}



четверг, 23 июля 2015 г.

C#. Извлечение ассоциированной с файлом иконки

[C#. Icon associated with files]

Большинство решений, которые я находил для получения иконки, ассоциированной с файлом, используют системные функции напрямую. Но есть и стандартные средства, с помощью которых это можно сделать наверное даже еще проще. А именно используя метод Icon.ExtractAssociatedIcon (https://msdn.microsoft.com/en-US/library/system.drawing.icon.extractassociatedicon.aspx)

Итак, небольшой и понятный код:


public class AssociatedIconService
{
    const string SUB_FOLDER = "AssociatedIcon";
    String tmpFolder = null;
    Dictionary<String, Icon> extToIconDict = new Dictionary<string, Icon>(); // cache

    public AssociatedIconService() {
        tmpFolder = Path.Combine(CommonInfo.AppTempFolderPath, SUB_FOLDER);
    }

    public Icon GetAssociatedIconByFileName(string fileName) {
        if (String.IsNullOrEmpty(fileName)) return null;

        // define key (short filename with extension)
        String shortFileName = "file";

        String extens = Path.GetExtension(fileName);
        if (String.IsNullOrEmpty(extens) == false)
            shortFileName += extens;

        if (extToIconDict.ContainsKey(shortFileName) == false) {
            // crete empty file if not exists
            String fullName = Path.Combine(tmpFolder, shortFileName);

            if (Directory.Exists(tmpFolder) == false) { Directory.CreateDirectory(tmpFolder); }
            if (File.Exists(fullName) == false) { File.Create(fullName).Dispose(); }

            // extract icon into cache
            extToIconDict[shortFileName] = Icon.ExtractAssociatedIcon(fullName);
        }

        return extToIconDict[shortFileName];
    }

}

четверг, 16 октября 2014 г.

Извлечение иконок из сборки библиотки DevExpress

[DevExpress Image Gallery File Extractor]

Библиотка элементов управления DevExpress для WinForms содержит сборку “DevExpress.Images”, иконки которой отобржаются в галерее картинок (DX Image Gallery). Эта иконки доступны в диалоге выбора картинок из ресурсов для контролов этой библиотки.

Ниже код для извлечения этих ресурсов в файловую систему.

private void buttonExtractImages_Click(object sender, EventArgs e) {
    String extractFodlerPath = null;

    FolderBrowserDialog fbd = new FolderBrowserDialog();
    if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        extractFodlerPath = fbd.SelectedPath;
    else
        return; 

    try {
        Assembly assembly = typeof(DevExpress.Images.DXImages).Assembly;
        foreach (String name in assembly.GetManifestResourceNames()) {
            using (Stream stream = assembly.GetManifestResourceStream(name)) {

                using (IResourceReader reader = new ResourceReader(stream)) {
                    foreach (DictionaryEntry entry in reader) {

                        String fileName = entry.Key.ToString().Replace("/", "_");

                        byte[] bytes = null;
                        using (UnmanagedMemoryStream ums = entry.Value as UnmanagedMemoryStream) {
                            bytes = new byte[ums.Length];
                            ums.Read(bytes, 0, bytes.Length);
                        }

                        FileInfo fi = new FileInfo(Path.Combine(extractFodlerPath, fileName));
                        using (FileStream fs = fi.OpenWrite()) {
                            fs.Write(bytes, 0, bytes.Length);
                        }
                    }
                }
            }
        }
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message, "Error");
    }
}

Результат выглядит следующим образом:


понедельник, 13 октября 2014 г.

Автоматическое изменение размеров пользовательского элемента управления, базирующегося на TokenEdit из библиотеки DevExpress (WinForms)

[Autosize of UserControl based on TokenEdit (DevExpress, WinForms)]

Еще один простой пример реализации пользовательского контрола, когда понадобилась поддержка его автоматического вертикального изменения в элементе управления LayoutControl. Для этого элемент управления должен нужным образом реализовывать интерфейс IXtraResizableControl.

Как это выглядит в деле:



Элемент управления простой, состоит из двух элементов - собственно TokenEdit и SimpleButton (в моем случае предусмотрен для редактирования списка элементов токена). Для TokenEdit свойство Anchor имеет значения "Top, Left, Right" и для SimpleButton значения "Left, Right".



Код реализации:

using DevExpress.Utils.Controls;
using DevExpress.XtraEditors;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication3
{
    public partial class UserControl1 : UserControl , IXtraResizableControl
    {
        public UserControl1() {
            InitializeComponent();

            this.Height = GetMaxHeight();
            this.tokenEdit1.Resize += tokenEdit1_Resize;
        }

        public TokenEdit TokenCtrl { get { return tokenEdit1; } }

        private int GetMaxHeight() {
            // выбираем наибольшее значение высоты из двух доступных элементов: TokenEdit или SimpleButton
            return simpleButton1.Height > tokenEdit1.Height ? simpleButton1.Height : tokenEdit1.Height;
        }

        int height = 0;
        void tokenEdit1_Resize(object sender, EventArgs e) {
            if (height != GetMaxHeight()) {
                height = GetMaxHeight();
                if (ChangedCore != null) ChangedCore(this, EventArgs.Empty);
            }
        }

        event EventHandler ChangedCore;
        event EventHandler IXtraResizableControl.Changed {
            add { ChangedCore += value; }
            remove { ChangedCore -= value; }
        }

        public bool IsCaptionVisible {
            get { return false; }
        }

        public Size MaxSize {
            get { return new Size(0, GetMaxHeight()); }
        }

        public Size MinSize {
            get { return new Size(0, GetMaxHeight()); }
        }
    }
}

Ну и на последок, инициализация элемента данными для теста:

using DevExpress.XtraEditors;
using System;
using System.Windows.Forms;

namespace WindowsFormsApplication3
{
    public partial class Form1 : Form
    {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            for (int i = 1; i < 10; i++) {

                TestObject obj1 = new TestObject();
                obj1.ID = i.ToString();
                obj1.Value = "Value " + i;

                userControl11.TokenCtrl.Properties.Tokens.Add(new TokenEditToken(obj1));
            }
        }

        class TestObject
        {
            public String ID { get; set; }
            public String Value { get; set; }

            public override string ToString() {
                return Value;
            }
        }
    }
}

пятница, 30 августа 2013 г.

Преобразование html в обычный текст при помощи элемента управления WebBrowser. C#, WinForms.

[Convert html to plain text using the WebBrowser control. C#, WinForms]

Для преобразования html в обычный текст без форматирования (также известный среди разработчиков как plain text), можно использовать много различных способов, от “ручного” парсинга html-тегов, до использования специальных библиотек для работы с html (самая известная среди них, пожалуй, “Html Agility Pack”). В интернете можно найти очень много примеров, одним из самых интересных способов, является на мой взгляд, применение xslt, где шаблон как фильтр исключает из результата ненужные теги.

Мои исходные данные были в бинарном формате (массив байтов: byte[]), причем данные были представлены в самых различных кодировках. Понятно, что основной проблемой становилось преобразование байтов в строку с учетом правильной кодировки. Вычитывать для этого charcterset “вручную” показалось мне слишко хлопотоным, поэтому я решил использовать элемент управления WebBrowser. Этот контрол базируется на установленном в системе Internet Explorer и справляется с черной работой за нас довольно таки неплохо.

Итак, все что нужно, это загрузить данные в элемент управления и получить значение свойства WebBrowser.Document.Body.InnerText.

Но и здесь, в таком простом сценарии, не обошлось без сюрпризов, с которыми мне пришлось некоторое время повозиться. Дело в том, что после установки источника данных, нельзя сразу обращаться к свойство Document.Body.InnerText, т.к. данные могут быть еще не загруженными, прежде нужно дождаться события DocumentCompleted. В следующем примере учитывается эта особенность. Обратите внимание на вызовы “Application.DoEvents()”, без них вызов события происходить не будет, программа зависает.

Также, хочу отметить, что при обработке значительной коллекции необходимо следить за своевременным освобождением ресурсов (использование Dispose или как здесь через using). Если этого не делать, память утекает, и программа падает. Так было у меня.


private String HtmlToPlainText(byte[] htmlBytes) {
    using (MemoryStream memStream = new MemoryStream(htmlBytes)) {
        memStream.Position = 0;

        using (WebBrowser webBrowser = new WebBrowser()) {
            webBrowser.ScriptErrorsSuppressed = true;

            bool loaded = false;
            webBrowser.DocumentCompleted += (x, y) => {
                loaded = true;
            };

            webBrowser.DocumentStream = memStream;

            // wait DocumentCompleted
            int waitCount = 0;
            Application.DoEvents(); // !!!
            while (loaded == false) {
                Thread.Sleep(200);
                Application.DoEvents(); // !!!
                waitCount++;
                if (waitCount * 50 >= 10000 /*ca. 10 sec*/ )
                    throw new Exception("timeout");
            }

            return webBrowser.Document.Body.InnerText;
        }
    }
}

воскресенье, 25 августа 2013 г.

Linq. Изменение порядка сортировки OrderBy без IComparer.

[Linq. Change the sort order by using OrderBy without IComparer]

Метод-расширение OrderBy предоставляет собой отличное и эффективное средство для упорядочивания коллекций, например, по строковому ключу. Можно использовать версию метода без компаратора, тогда используется его стандартная общая реализация. Все же, для строк рекомендуется использовать специальный строковой компаратор StringComparer, с помощью которого можно учитывать регистр, язык и региональные параметры. Но даже его в некоторых, казалось бы, элементарных случаях может не хватать. Например тогда, когда среди ключей есть нулевые значения, эти элементы занимают самые первые позиции. А мне нужно было их откинуть назад. Что делать? Казалось бы, пришло время браться за реализацию своего компаратора! Но, оказывается, можно обойтись без нее, нужно лишь правильно выбирать ключ, т.е. "подменять" его нулевые значения значением "последнего ряда". У меня это "xxxxxxxxxx".

Итак, для наглядности на примере:

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

namespace BlogApp
{
    class Program
    {
        static void Main(string[] args) {

            List<MyData> myDataList = new List<MyData>();
            myDataList.Add(new MyData() { Val1 = "D", Val2 = "1" });
            myDataList.Add(new MyData() { Val1 = "B", Val2 = "2" });
            myDataList.Add(new MyData() { Val1 = "A", Val2 = "3" });
            myDataList.Add(new MyData() { Val1 = "D", Val2 = "4" });
            myDataList.Add(new MyData() { Val1 = null, Val2 = "5" });
            myDataList.Add(new MyData() { Val1 = null, Val2 = "6" });
            myDataList.Add(new MyData() { Val1 = "C", Val2 = "7" });
            myDataList.Add(new MyData() { Val1 = "B", Val2 = "8" });

            var res1 = myDataList.OrderBy(x => x.Val1);
            var res2 = myDataList.OrderBy(x => x.Val1 == null ? "xxxxxxxxxx" : x.Val1); // !!!!!!!

            Console.WriteLine("Result 1:");
            Console.WriteLine(String.Join(Environment.NewLine, res1.Select(x => String.Format("{0}:{1}", x.Val2, x.Val1))));
            Console.WriteLine();
            Console.WriteLine("Result 2:");
            Console.WriteLine(String.Join(Environment.NewLine, res2.Select(x => String.Format("{0}:{1}", x.Val2, x.Val1))));
            Console.ReadLine();
        }
    }

    public class MyData
    {
        public String Val1 { get; set; }
        public String Val2 { get; set; }
    }
}

Результат: