System.Net.HttpClient get için sorgu dizesi oluşturun


184

System.Net.HttpClient kullanarak bir http alma isteği göndermek istiyorsanız, parametre eklemek için hiçbir api var gibi görünüyor, bu doğru mu?

Bir ad değeri toplama ve bunları kodlama url oluşturma ve sonra nihayet onları birleştirmek içermeyen sorgu dizesi oluşturmak için kullanılabilir herhangi bir basit API var mı? RestSharp'ın api (yani AddParameter (..)) gibi bir şey kullanmayı umuyordum


@Michael Perrenoud, kodlamayı gerektiren karakterlerle kabul edilen yanıtı kullanarak yeniden düşünmek isteyebilirsiniz, aşağıdaki açıklamamı inceleyin
illegal-göçmen

Yanıtlar:


309

System.Net.HttpClient kullanarak bir http alma isteği göndermek istiyorsanız, parametre eklemek için hiçbir api var gibi görünüyor, bu doğru mu?

Evet.

Bir ad değeri toplama ve bunları kodlama url oluşturma ve sonra nihayet onları birleştirmek içermeyen sorgu dizesi oluşturmak için kullanılabilir herhangi bir basit API var mı?

Elbette:

var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();

size beklenen sonucu verecektir:

foo=bar%3c%3e%26-baz&bar=bazinga

Ayrıca UriBuildersınıfı yararlı bulabilirsiniz :

var builder = new UriBuilder("http://example.com");
builder.Port = -1;
var query = HttpUtility.ParseQueryString(builder.Query);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();

size beklenen sonucu verecektir:

http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga

HttpClient.GetAsyncyönteminize güvenli bir şekilde besleyebileceğinizden emin olabilirsiniz .


9
Bu, .NET'te URL işleme açısından mutlak en iyisidir. Hiç url kodlama ve dize birleştirme veya dize oluşturucu veya herhangi bir şey yapmaya gerek yok. UriBuilder sınıfı #, Fragment özelliğini kullanarak sizin için fragmanlı ( ) URL'leri bile işleyecektir . Yerleşik araçları kullanmak yerine URL'leri elle işleme hatasını yapan çok sayıda insan gördüm.
Darin Dimitrov

6
NameValueCollection.ToString()normalde sorgu dizeleri yapmaz ToStringve sonuçta bir yapmanın ParseQueryStringyeni bir sorgu dizesiyle sonuçlanacağını belirten herhangi bir belge yoktur , bu nedenle bu işlevsellikte garanti olmadığından herhangi bir zamanda kırılabilir.
Matthew

11
HttpUtility taşınabilir çalışma zamanında kullanılamayan System.Web'de bulunmaktadır. Bu işlevselliğin daha genel olarak sınıf kütüphanelerinde bulunmaması garip görünüyor.
Chris Eldredge

82
Bu çözüm aşağılıktır. Net düzgün sorgu dizesi oluşturucu olmalıdır.
Kugel

8
En iyi çözümün, yalnızca boş dizede geçen bir yardımcı program yöntemini çağırabileceğiniz iç sınıfta gizlenmiş olması, tam olarak zarif bir çözüm olarak adlandırılamaz.
Kugel

79

Dahil etmek istemeyen olanlar için System.Webzaten bunu kullanmıyorum projelerde kullanabileceğiniz FormUrlEncodedContentgelen System.Net.Httpve aşağıdaki gibi bir şey yapmak:

keyvaluepair sürümü

string query;
using(var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
    new KeyValuePair<string, string>("ham", "Glazed?"),
    new KeyValuePair<string, string>("x-men", "Wolverine + Logan"),
    new KeyValuePair<string, string>("Time", DateTime.UtcNow.ToString()),
})) {
    query = content.ReadAsStringAsync().Result;
}

sözlük sürümü

string query;
using(var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
    { "ham", "Glaced?"},
    { "x-men", "Wolverine + Logan"},
    { "Time", DateTime.UtcNow.ToString() },
})) {
    query = content.ReadAsStringAsync().Result;
}

Neden using ifadesi kullanıyorsunuz?
Ian Warburton

Kaynakları serbest bırakma olasılığı yüksek, ancak bu üst düzey. Bunu yapma.
Kody

5
Bu, KVP dizisi yerine <string, string> sözlüğü kullanılarak daha özlü olabilir. Ardından şu başlatıcı sözdizimini kullanarak: {"ham", "Glazed?" }
Sean B

@SeanB Özellikle dinamik / bilinmeyen bir parametre listesi eklemek için bir şey kullanırken bu iyi bir fikir. Bu örnek için "sabit" bir liste olduğundan, bir sözlüğün yükünün buna değdiğini hissetmedim.
Rostov

6
@Kody Neden kullanmıyorsun dispose? Tekrar kullanmak gibi iyi bir nedenim olmadıkça her zaman elden çıkarırım HttpClient.
Dan Friedman

41

TL; DR: kabul edilen sürümü kullanmayın, çünkü unicode karakterlerin işlenmesiyle tamamen kırılmıştır ve asla dahili API kullanmaz

Aslında kabul edilen çözümle garip çift kodlama sorunu buldum:

Dolayısıyla, kodlanması gereken karakterlerle uğraşıyorsanız, kabul edilen çözüm çift kodlamaya yol açar:

  • sorgu parametreleri NameValueCollectionindeksleyici kullanılarak otomatik olarak kodlanır ( ve bu kullanım UrlEncodeUnicode, normal beklenen değil UrlEncode(!) )
  • Ardından, bir kez daha kodlama yapan bir yapıcı kullanarak uriBuilder.Uriyeni oluştururUri (normal url kodlaması)
  • Bunu yaparakuriBuilder.ToString() (bu, UriIMO'nun en azından tutarsızlık, belki bir hata, ancak bu başka bir soru olduğu doğru döndürülse de) ve ardından HttpClientdize kabul eden yöntemi kullanarak - istemci yine Uride şu şekilde geçirilen dizeden oluşturur :new Uri(uri, UriKind.RelativeOrAbsolute)

Küçük ama tam repro:

var builder = new UriBuilder
{
    Scheme = Uri.UriSchemeHttps,
    Port = -1,
    Host = "127.0.0.1",
    Path = "app"
};

NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);

query["cyrillic"] = "кирилиця";

builder.Query = query.ToString();
Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want

var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more
Console.WriteLine(uri);

// this is still wrong:
var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice)
new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch!

Çıktı:

?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f

https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f

Gördüğünüz gibi, uribuilder.ToString()+ httpClient.GetStringAsync(string)veya uriBuilder.Uri+ yaparsanız yapınhttpClient.GetStringAsync(Uri) yaparsanız yapın, çift kodlanmış parametre gönderirsiniz

Sabit örnek şunlar olabilir:

var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);

Ama bu kullanır eskimiş Uri yapıcısı

PS, Windows Server'daki en son Uri.NET'imde, bool doc yorumuna sahip yapıcı "eski, dontEscape her zaman yanlıştır" diyor, ancak aslında beklendiği gibi çalışıyor (kaçıyor)

Yani başka bir hata gibi görünüyor ...

Ve bu bile yanlıştır - UrlEncodedUnicode'u sunucuya gönderir, sadece UrlEncoded sunucunun beklediği gibi değil

Güncelleme: bir şey daha var, NameValueCollection aslında artık kullanılmaması gereken ve normal url.encode / decode ile uyumsuz olan UrlEncodeUnicode yapıyor (bkz . URL Sorgusu için NameValueCollection? ).

İşin özü Yani: Bu kesmek kullanmak aslaNameValueCollection query = HttpUtility.ParseQueryString(builder.Query); sizin unicode sorgu parametreleri karmaşa olacak şekilde. Sorguyu manuel olarak derleyin ve UriBuilder.Querygerekli kodlamayı yapacak olan atamayı yapın ve ardından Uri'yi kullanın UriBuilder.Uri.

Bunun gibi kullanılması gerekmeyen kodu kullanarak kendinizi incitmenin ana örneği


16
Bu cevaba tam bir yardımcı program işlevi ekleyebilir misiniz?
mafu

8
Bunun üzerine ikinci mafu: Cevabı okudum ama bir sonucum yok. Buna kesin bir cevap var mı?
Richard Griffiths

3
Ben de bu sorunun kesin cevabını görmek istiyorum
Pones

Bu sorunun kesin cevabı kullanmaktır var namedValues = HttpUtility.ParseQueryString(builder.Query), ancak daha sonra döndürülen NameValueCollection'ı kullanmak yerine, hemen böyle bir var dic = values.ToDictionary(x => x, x => values[x]); Sözlüğe dönüştürün: Sözlüğe yeni değerler ekleyin, ardından yapıcıya iletin FormUrlEncodedContentve çağırın ReadAsStringAsync().Result. Bu, UriBuilder'a geri atayabileceğiniz düzgün kodlanmış bir sorgu dizesi sağlar.
Triynko

Aslında olabilir sadece yerine bütün bunlar NamedValueCollection.ToString kullanabilirsiniz, ancak yalnızca değiştirirseniz bir app.config / web.config bu ayarı kullanarak önler ASP.NET '% uXXXX' kodlama: <add key="aspnet:DontUsePercentUUrlEncoding" value="true" />. Bu davranışa bağımlı olmazdım, bu nedenle FormUrlEncodedContent sınıfını daha önceki bir yanıtla gösterildiği gibi kullanmak daha iyidir: stackoverflow.com/a/26744471/88409
Triynko

41

ASP.NET Core projesinde QueryHelpers sınıfını kullanabilirsiniz.

// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
    ["foo"] = "bar",
    ["foo2"] = "bar2",
    // ...
};

var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));

2
Bu işlemle yine de aynı anahtar için birden fazla değer gönderememeniz can sıkıcı. Sadece foo'nun bir parçası olarak "bar" ve "bar2" göndermek istiyorsanız, bu mümkün değildir.
m0g

2
Bu modern uygulamalar için harika bir cevap, senaryomda çalışıyor, basit ve temiz. Ancak, herhangi bir kaçış mekanizmasına ihtiyacım yok - test edilmedi.
Patrick Stalph

Bu NuGet paketi .NET standardı 2.0'ı hedefliyor, yani tam .NET framework 4.6.1+ üzerinde kullanabilirsiniz
eddiewould

24

Flurl'a göz atmak isteyebilirsiniz .

var result = await "https://api.com"
    // basic URL building:
    .AppendPathSegment("endpoint")
    .SetQueryParams(new {
        api_key = ConfigurationManager.AppSettings["SomeApiKey"],
        max_results = 20,
        q = "Don't worry, I'll get encoded!"
    })
    .SetQueryParams(myDictionary)
    .SetQueryParam("q", "overwrite q!")

    // extensions provided by Flurl.Http:
    .WithOAuthBearerToken("token")
    .GetJsonAsync<TResult>();

Daha fazla bilgi için dokümanlara göz atın . Tam paket NuGet'te mevcuttur:

PM> Install-Package Flurl.Http

veya yalnızca bağımsız URL oluşturucu:

PM> Install-Package Flurl


2
Neden Urikendi sınıfınızı genişletmeyin veya onunla başlamıyorsunuz string?
mpen

2
Teknik olarak kendi Urlsınıfımla başladım . Yukarıdaki new Url("https://api.com").AppendPathSegment...Şahsen eşdeğerdir Daha az tuş vuruşu nedeniyle dize uzantılarını tercih ediyorum ve dokümanlar üzerinde standartlaştırılmış, ancak her iki şekilde de yapabilirsiniz.
Todd Menier

Konu dışı, ama gerçekten güzel bir lib, bunu gördükten sonra kullanıyorum. IHttpClientFactory'yi kullandığınız için teşekkür ederiz.
Ed S.

4

Rostov'un gönderisiyle aynı satırlar boyunca, System.Webprojenize bir referans eklemek istemiyorsanız FormDataCollection,System.Net.Http.Formatting ve aşağıdaki gibi bir şey yapmak:

kullanma System.Net.Http.Formatting.FormDataCollection

var parameters = new Dictionary<string, string>()
{
    { "ham", "Glaced?" },
    { "x-men", "Wolverine + Logan" },
    { "Time", DateTime.UtcNow.ToString() },
}; 
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();

3

Darin ilginç ve akıllı bir çözüm sundu ve işte başka bir seçenek olabilecek bir şey var:

public class ParameterCollection
{
    private Dictionary<string, string> _parms = new Dictionary<string, string>();

    public void Add(string key, string val)
    {
        if (_parms.ContainsKey(key))
        {
            throw new InvalidOperationException(string.Format("The key {0} already exists.", key));
        }
        _parms.Add(key, val);
    }

    public override string ToString()
    {
        var server = HttpContext.Current.Server;
        var sb = new StringBuilder();
        foreach (var kvp in _parms)
        {
            if (sb.Length > 0) { sb.Append("&"); }
            sb.AppendFormat("{0}={1}",
                server.UrlEncode(kvp.Key),
                server.UrlEncode(kvp.Value));
        }
        return sb.ToString();
    }
}

ve bunu kullanırken, bunu yapabilirsiniz:

var parms = new ParameterCollection();
parms.Add("key", "value");

var url = ...
url += "?" + parms;

5
Tam sorgu dizgisinde değil, for döngüsünün içinde kvp.Keyve kvp.Valueayrı ayrı kodlamak istersiniz (böylece &ve =karakterlerini kodlamamaktadır ).
Matthew

Teşekkürler Mike. Diğer önerilen çözümler (NameValueCollection dahil) benim için işe yaramadı çünkü bir PCL projesindeyim, bu mükemmel bir alternatifti. İstemci tarafında çalışıyoruz Diğerleri için server.UrlEncodedeğiştirilebilirWebUtility.UrlEncode
BCA

2

Veya sadece Uri uzantımı kullanarak

kod

public static Uri AttachParameters(this Uri uri, NameValueCollection parameters)
{
    var stringBuilder = new StringBuilder();
    string str = "?";
    for (int index = 0; index < parameters.Count; ++index)
    {
        stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]);
        str = "&";
    }
    return new Uri(uri + stringBuilder.ToString());
}

kullanım

Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection
                                                                           {
                                                                               {"Bill", "Gates"},
                                                                               {"Steve", "Jobs"}
                                                                           });

Sonuç

http://www.example.com/index.php?Bill=Gates&Steve=Jobs


27
URL kodlamasını unuttunuz mu?
Kugel

1
bu, açık ve kullanışlı yardımcılar oluşturmak için uzantıların kullanılmasına harika bir örnektir. Eğer bu bir katı RestClient bina için yolda konum kabul cevap birleştirirseniz
emran

2

Geliştirdiğim RFC 6570 URI Şablon kütüphanesi bu işlemi gerçekleştirebilir. Tüm kodlama bu RFC'ye göre sizin için yapılır. Bu yazı yazıldığı sırada beta sürümü mevcuttur ve kararlı bir 1.0 sürümü olarak değerlendirilmemesinin tek nedeni, belgelerin beklentilerimi tam olarak karşılamamasıdır (bkz. # 17 , # 18 , # 32 , # 43 ).

Yalnızca bir sorgu dizesi oluşturabilirsiniz:

UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
  {
    { "param1", "value1" },
    { "param2", "value2" },
  };
Uri relativeUri = template.BindByName(parameters);

Veya tam bir URI oluşturabilirsiniz:

UriTemplate template = new UriTemplate("path/to/item{?params*}");
var parameters = new Dictionary<string, string>
  {
    { "param1", "value1" },
    { "param2", "value2" },
  };
Uri baseAddress = new Uri("http://www.example.com");
Uri relativeUri = template.BindByName(baseAddress, parameters);

1

Bu birkaç kez yeniden kullanmak zorunda olduğundan, sadece sorgu dizesinin nasıl oluşturulduğunu soyutlamak için yardımcı olan bu sınıf ile geldi.

public class UriBuilderExt
{
    private NameValueCollection collection;
    private UriBuilder builder;

    public UriBuilderExt(string uri)
    {
        builder = new UriBuilder(uri);
        collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
    }

    public void AddParameter(string key, string value) {
        collection.Add(key, value);
    }

    public Uri Uri{
        get
        {
            builder.Query = collection.ToString();
            return builder.Uri;
        }
    }

}

Kullanım böyle bir şeye basitleştirilecektir:

var builder = new UriBuilderExt("http://example.com/");
builder.AddParameter("foo", "bar<>&-baz");
builder.AddParameter("bar", "second");
var uri = builder.Uri;

uri döndürür: http://example.com/?foo=bar%3c%3e%26-baz&bar=second


1

Taras.roshko'nun cevabında açıklanan çift kodlama sorununu önlemek ve sorgu parametreleriyle kolayca çalışabilmek için uriBuilder.Uri.ParseQueryString()bunun yerine kullanabilirsiniz HttpUtility.ParseQueryString().


1

Kabul edilen yanıtın iyi bir parçası, HttpUtility.ParseQueryString () yerine UriBuilder.Uri.ParseQueryString () kullanacak şekilde değiştirildi:

var builder = new UriBuilder("http://example.com");
var query = builder.Uri.ParseQueryString();
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();

Bilginize: Bu başvuru gerektirir System.Net.Http olarak ParseQueryString()uzantısı yöntemi içinde değil System.
Sunny Patel

0

"Darin Dimitrov" sayesinde, Bu uzatma yöntemleridir.

 public static partial class Ext
{
    public static Uri GetUriWithparameters(this Uri uri,Dictionary<string,string> queryParams = null,int port = -1)
    {
        var builder = new UriBuilder(uri);
        builder.Port = port;
        if(null != queryParams && 0 < queryParams.Count)
        {
            var query = HttpUtility.ParseQueryString(builder.Query);
            foreach(var item in queryParams)
            {
                query[item.Key] = item.Value;
            }
            builder.Query = query.ToString();
        }
        return builder.Uri;
    }

    public static string GetUriWithparameters(string uri,Dictionary<string,string> queryParams = null,int port = -1)
    {
        var builder = new UriBuilder(uri);
        builder.Port = port;
        if(null != queryParams && 0 < queryParams.Count)
        {
            var query = HttpUtility.ParseQueryString(builder.Query);
            foreach(var item in queryParams)
            {
                query[item.Key] = item.Value;
            }
            builder.Query = query.ToString();
        }
        return builder.Uri.ToString();
    }
}

-1

Bir sözlüğü QueryStringFormat dönüştürmek için bir uzantı yöntemi oluşturmaktan daha iyi bir çözüm bulamadım. Waleed AK tarafından önerilen çözüm de iyidir.

Çözümümü takip et:

Uzantı yöntemini oluşturun:

public static class DictionaryExt
{
    public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
    {
        return ToQueryString<TKey, TValue>(dictionary, "?");
    }

    public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, string startupDelimiter)
    {
        string result = string.Empty;
        foreach (var item in dictionary)
        {
            if (string.IsNullOrEmpty(result))
                result += startupDelimiter; // "?";
            else
                result += "&";

            result += string.Format("{0}={1}", item.Key, item.Value);
        }
        return result;
    }
}

Ve onları:

var param = new Dictionary<string, string>
          {
            { "param1", "value1" },
            { "param2", "value2" },
          };
param.ToQueryString(); //By default will add (?) question mark at begining
//"?param1=value1&param2=value2"
param.ToQueryString("&"); //Will add (&)
//"&param1=value1&param2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1&param2=value2"

1
Bu çözümde parametrelerin doğru URL kodlaması eksik ve 'geçersiz' karakterler içeren değerlerle çalışmaz
Xavier Poinas

Cevabı güncellemek ve eksik kodlama satırını eklemek için çekinmeyin, sadece bir kod satırı!
Diego Mendes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.