Версия HttpUtility для переносимой библиотеки классов (PCL) .ParseQueryString

Есть ли версия HttpUtility.ParseQueryString для переносимой библиотеки классов (PCL), содержащаяся в System.Web, или какой-то код, который я мог бы использовать? Я хочу прочитать очень сложный URL.


person Muhammad Rehan Saeed    schedule 28.11.2013    source источник
comment
Что не так со ссылкой на System.Web?   -  person Ben    schedule 28.11.2013
comment
Вы можете показать нам свой сложный URL   -  person Indranil.Bharambe    schedule 28.11.2013
comment
Я не могу ссылаться на System.Web в проекте переносимой библиотеки классов.   -  person Muhammad Rehan Saeed    schedule 28.11.2013
comment
Это пример более простого URL-адреса, который я пытаюсь проанализировать   -  person Muhammad Rehan Saeed    schedule 28.11.2013


Ответы (4)


HttpUtility.ParseQueryString возвращает HttpValueCollection (внутренний класс), который наследуется от NameValueCollection. NameValueCollection - это набор пар "ключ-значение", подобный словарю, но он поддерживает дубликаты, поддерживает порядок и реализует только IEnumerable (этот набор является предварительным обобщением). NameValueCollection не поддерживается в PCL.

Мое решение (частично заимствованное и измененное из .NET framework) - заменить HttpValueCollection на Collection<HttpValue>, где HttpValue - это просто пара значений ключа.

public sealed class HttpUtility
{
    public static HttpValueCollection ParseQueryString(string query)
    {
        if (query == null)
        {
            throw new ArgumentNullException("query");
        }

        if ((query.Length > 0) && (query[0] == '?'))
        {
            query = query.Substring(1);
        }

        return new HttpValueCollection(query, true);
    }
}

public sealed class HttpValue
{
    public HttpValue()
    {
    }

    public HttpValue(string key, string value)
    {
        this.Key = key;
        this.Value = value;
    }

    public string Key { get; set; }
    public string Value { get; set; }
}

public class HttpValueCollection : Collection<HttpValue>
{
    #region Constructors

    public HttpValueCollection()
    {
    }

    public HttpValueCollection(string query)
        : this(query, true)
    {
    }

    public HttpValueCollection(string query, bool urlencoded)
    {
        if (!string.IsNullOrEmpty(query))
        {
            this.FillFromString(query, urlencoded);
        }
    } 

    #endregion

    #region Parameters

    public string this[string key]
    {
        get { return this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value; }
        set { this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value = value; }
    }

    #endregion

    #region Public Methods

    public void Add(string key, string value)
    {
        this.Add(new HttpValue(key, value));
    }

    public bool ContainsKey(string key)
    {
        return this.Any(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
    }

    public string[] GetValues(string key)
    {
        return this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).ToArray();
    }

    public void Remove(string key)
    {
        this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase))
            .ToList()
            .ForEach(x => this.Remove(x));
    }

    public override string ToString()
    {
        return this.ToString(true);
    }

    public virtual string ToString(bool urlencoded)
    {
        return this.ToString(urlencoded, null);
    }

    public virtual string ToString(bool urlencoded, IDictionary excludeKeys)
    {
        if (this.Count == 0)
        {
            return string.Empty;
        }

        StringBuilder stringBuilder = new StringBuilder();

        foreach (HttpValue item in this)
        {
            string key = item.Key;

            if ((excludeKeys == null) || !excludeKeys.Contains(key))
            {
                string value = item.Value;

                if (urlencoded)
                {
                    // If .NET 4.5 and above (Thanks @Paya)
                    key = WebUtility.UrlDecode(key);
                    // If .NET 4.0 use this instead.
                    // key = Uri.EscapeDataString(key);
                }

                if (stringBuilder.Length > 0)
                {
                    stringBuilder.Append('&');
                }

                stringBuilder.Append((key != null) ? (key + "=") : string.Empty);

                if ((value != null) && (value.Length > 0))
                {
                    if (urlencoded)
                    {
                        value = Uri.EscapeDataString(value);
                    }

                    stringBuilder.Append(value);
                }
            }
        }

        return stringBuilder.ToString();
    } 

    #endregion

    #region Private Methods

    private void FillFromString(string query, bool urlencoded)
    {
        int num = (query != null) ? query.Length : 0;
        for (int i = 0; i < num; i++)
        {
            int startIndex = i;
            int num4 = -1;
            while (i < num)
            {
                char ch = query[i];
                if (ch == '=')
                {
                    if (num4 < 0)
                    {
                        num4 = i;
                    }
                }
                else if (ch == '&')
                {
                    break;
                }
                i++;
            }
            string str = null;
            string str2 = null;
            if (num4 >= 0)
            {
                str = query.Substring(startIndex, num4 - startIndex);
                str2 = query.Substring(num4 + 1, (i - num4) - 1);
            }
            else
            {
                str2 = query.Substring(startIndex, i - startIndex);
            }

            if (urlencoded)
            {
                this.Add(Uri.UnescapeDataString(str), Uri.UnescapeDataString(str2));
            }
            else
            {
                this.Add(str, str2);
            }

            if ((i == (num - 1)) && (query[i] == '&'))
            {
                this.Add(null, string.Empty);
            }
        }
    } 

    #endregion
}

ОБНОВЛЕНИЕ

Обновлено так, что HttpValueCollection теперь наследуется от Collection, а не List, как выделено в комментариях.

ОБНОВЛЕНИЕ 2

Обновлено для использования WebUtility.UrlDecode при использовании .NET 4.5 благодаря @Paya.

person Muhammad Rehan Saeed    schedule 29.11.2013
comment
предупреждение: наследование от списка ‹T› - person Display Name; 27.11.2014
comment
У меня это почти работает, но мне не хватает .ForEach в Remove (строковый ключ) - person tofutim; 06.03.2015
comment
var list = this.Where (x = ›string.Equals (x.Key, key, StringComparison.OrdinalIgnoreCase)) .ToList (); foreach (var x в списке) {this.Remove (x); } - person tofutim; 06.03.2015
comment
WebUtility.UrlDecode кажется лучшим выбором, если вы используете .NET 4.5 - person Paya; 16.04.2015

Вы также можете реализовать это так:

public static class HttpUtility
{
    public static Dictionary<string, string> ParseQueryString(Uri uri)
    {
        var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1); // +1 for skipping '?'
        var pairs = query.Split('&');
        return pairs
            .Select(o => o.Split('='))
            .Where(items => items.Count() == 2)
            .ToDictionary(pair => Uri.UnescapeDataString(pair[0]),
                pair => Uri.UnescapeDataString(pair[1]));
    }
}

Вот для этого модульный тест:

public class HttpParseQueryValuesTests
{
    [TestCase("http://www.example.com", 0, "", "")]
    [TestCase("http://www.example.com?query=value", 1, "query", "value")]
    public void When_parsing_http_query_then_should_have_these_values(string uri, int expectedParamCount,
        string expectedKey, string expectedValue)
    {
        var queryParams = HttpUtility.ParseQueryString(new Uri(uri));
        queryParams.Count.Should().Be(expectedParamCount);

        if (queryParams.Count > 0)
            queryParams[expectedKey].Should().Be(expectedValue);
    }
}
person Martin Komischke    schedule 13.09.2014
comment
Недостатком этого подхода является то, что он не допускает аргументов с повторяющимися ключами. Кроме того, он не обрабатывает аргументы, будет пустыми значениями. Однако это, вероятно, будет соответствовать потребностям большинства людей с большей простотой. - person Muhammad Rehan Saeed; 15.09.2014
comment
В спецификации URL-адреса нет ничего, что останавливает использование повторяющихся ключей (см. этот вопрос). - person Muhammad Rehan Saeed; 07.04.2016

Моя библиотека Flurl - это PCL, который анализирует строки запроса в IDictionary<string, object>, когда вы создаете экземпляр объекта Url из строки:

using Flurl;

var url = new Url("http://...");
// get values from url.QueryParams dictionary

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

person Todd Menier    schedule 25.09.2014

Сегодня я сделал пакет nuget, который выполняет базовое построение и анализ запросов. Он предназначен для личного использования, но доступен в репозитории nuget.com. Для личного использования означает, что он может не полностью соответствовать «спецификациям HTTP-запросов». Ссылка на Nuget здесь

Он основан на словаре, поэтому не поддерживает повторяющиеся ключи, в основном потому, что я не знаю, зачем вам это ... (может ли кто-нибудь просветить меня?)

У него есть 1 класс, представляющий запрос, который поддерживает добавление, получение параметров, проверку наличия ключа ... И статический метод для анализа ключа и возврата экземпляра запроса.

person Stan Wijckmans    schedule 04.05.2016
comment
1. Использование словаря вызовет исключения с дублированием ключей, если вы не будете обрабатывать повторяющиеся ключи, это может вызвать ошибку на вашем сайте. Хакеры часто пытаются привести сайты к ошибкам, передавая случайные данные, в том числе повторяющиеся ключи. 2. Использование дублирующего ключа - еще один способ приема коллекций вещей. Как правило, для этого вам нужно использовать значения, разделенные запятыми. - person Muhammad Rehan Saeed; 05.05.2016
comment
Я обрабатываю исключение, которое выдает словарь при добавлении повторяющегося ключа. В таком случае я заменяю значение новым, так что хакерская часть здесь не проблема. Я никогда не знал, что 2 возможно. Является ли сбор данных с повторяющимися ключами стандартизированным способом? Если это так, я посмотрю на это. - person Stan Wijckmans; 08.05.2016