System.Net.HttpClient get에 대한 쿼리 문자열 작성


184

System.Net.HttpClient를 사용하여 http get 요청을 제출하려면 매개 변수를 추가 할 API가없는 것 같습니다. 맞습니까?

이름 값 수집 및 URL 인코딩을 생성 한 다음 연결하는 쿼리 문자열을 작성하는 데 사용할 수있는 간단한 API가 있습니까? RestSharp의 api (예 : AddParameter (..))와 같은 것을 사용하고 싶었습니다.


@Michael Perrenoud 당신은 인코딩이 필요한 문자로 허용 된 답변을 사용하여 재고를 원할 수 있습니다. 아래 내 설명을 참조하십시오
불법 이민자

답변:


309

System.Net.HttpClient를 사용하여 http get 요청을 제출하려면 매개 변수를 추가 할 API가없는 것 같습니다. 맞습니까?

예.

이름 값 수집 및 URL 인코딩을 생성 한 다음 연결하는 쿼리 문자열을 작성하는 데 사용할 수있는 간단한 API가 있습니까?

확실한:

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

예상 결과를 제공합니다.

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

UriBuilder클래스가 유용하다는 것을 알 수 있습니다 .

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();

예상 결과를 제공합니다.

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

HttpClient.GetAsync분석법에 안전하게 공급할 수 있습니다 .


9
.NET의 URL 처리 측면에서 최고입니다. 수동으로 URL을 인코딩하거나 문자열 연결 또는 문자열 작성기 등을 수행 할 필요가 없습니다. UriBuilder 클래스는 #Fragment 속성을 사용하여 조각 ( )이있는 URL도 처리 합니다. 내장 도구를 사용하는 대신 URL을 수동으로 처리하는 실수를하는 사람들이 너무 많습니다.
Darin Dimitrov 2016 년

6
NameValueCollection.ToString()일반적으로 쿼리 문자열을하고 일을한다는에는 문서가 없습니다하지 않는 ToString결과에하면 ParseQueryString해당 기능에 보장이 없습니다로 언제든지 깨질 수 따라서 새로운 쿼리 문자열이 발생할 것입니다.
Matthew

11
HttpUtility는 System.Web에 있으며 이식 가능한 런타임에서는 사용할 수 없습니다. 이 기능이 클래스 라이브러리에서 더 일반적으로 사용 가능하지 않은 것은 이상합니다.
Chris Eldredge

82
이 솔루션은 비열합니다. .Net에는 적절한 쿼리 문자열 빌더가 있어야합니다.
Kugel

8
빈 문자열로 전달되는 유틸리티 메소드를 호출해야만 얻을 수있는 내부 클래스에 최상의 솔루션이 숨겨져 있다는 사실을 정확하게 우아한 솔루션이라고 부를 수는 없습니다.
Kugel

79

포함하지 않는 사람들을 위해 System.Web이미 그것을 사용하지 않는 프로젝트에, 당신은 사용할 수 있습니다 FormUrlEncodedContent에서 System.Net.Http하고 다음과 같은 일을 할 :

키값 쌍 버전

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;
}

사전 버전

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;
}

using 문을 사용하는 이유는 무엇입니까?
Ian Warburton

자원을 확보 할 가능성이 있지만 이는 최고입니다. 이러지 마
Kody

5
KVP 배열 대신 Dictionary <string, string>을 사용하면 더 간결 할 수 있습니다. 그런 다음 초기화 구문을 사용합니다 : { "ham", "Glazed?" }
Sean B

@SeanB 특히 동적 / 알 수없는 파라미터 목록을 추가 할 때 사용하는 것이 좋습니다. 이 예에서는 "고정"목록이므로 사전의 오버 헤드가 그만한 가치가 있다고 생각하지 않았습니다.
Rostov

6
@Kody 왜 사용하지 말라고 dispose합니까? 나는 재사용과 같은 좋은 이유가없는 한 항상 처분한다 HttpClient.
Dan Friedman

41

TL; DR : 유니 코드 문자 처리와 관련하여 완전히 손상되어 내부 API를 사용하지 않으므로 허용되는 버전을 사용하지 마십시오

실제로 허용 된 솔루션에서 이상한 이중 인코딩 문제를 발견했습니다.

따라서 인코딩 해야하는 문자를 처리하는 경우 허용되는 솔루션은 이중 인코딩으로 이어집니다.

  • 쿼리 매개 변수가 자동으로 사용하여 인코딩 된 NameValueCollection인덱서 ( 이 용도 UrlEncodeUnicode가 아닌 일반 예상 UrlEncode(!) )
  • 그런 다음 호출 uriBuilder.Uri하면 인코딩을 한 번 더하는Uri 생성자 사용하여 새로 만듭니다 (일반 URL 인코딩)
  • 즉,이 일을 피할 수 없습니다uriBuilder.ToString() (이 반환 수정하더라도 Uri사용 후 IMO 적어도 불일치, 어쩌면 버그이다,하지만 다른 질문) 및 HttpClient문자열을 받아들이는 방법 - 클라이언트가 아직 생성 Uri이처럼 전달 된 문자열의 아웃 :new Uri(uri, UriKind.RelativeOrAbsolute)

작지만 완전한 재현 :

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!

산출:

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

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

보시다시피 uribuilder.ToString()+ httpClient.GetStringAsync(string)또는 uriBuilder.Uri+에 관계없이 httpClient.GetStringAsync(Uri)이중 인코딩 된 매개 변수를 보내 게됩니다.

고정 된 예는 다음과 같습니다.

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

그러나 이것은 쓸모없는 Uri 생성자를 사용합니다.

Windows Server의 최신 .NET에서 PS, Uribool doc 주석이있는 생성자는 "사용되지 않음, dontEscape는 항상 false"라고 말하지만 실제로 예상대로 작동합니다 (건너 뛰기)

다른 버그처럼 보입니다.

그리고 이것은 심지어 틀린 것입니다-서버가 기대하는 UrlEncoded뿐만 아니라 UrlEncodedUnicode를 서버로 보냅니다.

업데이트 : 한 가지 더, NameValueCollection은 실제로 더 이상 사용되지 않으며 일반 url.encode / decode와 호환되지 않는 UrlEncodeUnicode를 실제로 수행합니다 ( NameValueCollection to URL Query? 참조 ).

결론은 유니 코드 쿼리 매개 변수를 망칠 것이므로이 핵을 사용하지 마십시오NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); . 쿼리를 수동으로 빌드하고 UriBuilder.Query필요한 인코딩을 수행 한 다음 Uri를 사용하여 쿼리를 지정하십시오 UriBuilder.Uri.

이와 같이 사용해서는 안되는 코드를 사용하여 자신을 해치는 주요 예


16
이 답변에 완전한 유틸리티 기능을 추가 할 수 있습니까?
mafu

8
나는 이것에 대한 두 번째 마푸 : 대답을 읽었지만 결론이 없습니다. 이것에 대한 확실한 대답이 있습니까?
Richard Griffiths

3
또한이 문제에 대한 결정적인 답변을보고 싶습니다
Pones

이 문제에 대한 확실한 대답은을 사용하는 var namedValues = HttpUtility.ParseQueryString(builder.Query)것이지만 반환 된 NameValueCollection을 사용하는 대신 즉시 다음과 같이 Dictionary로 변환하십시오. 사전에 var dic = values.ToDictionary(x => x, x => values[x]); 새 값을 추가 한 다음 생성자에 전달 FormUrlEncodedContent하고 호출 ReadAsStringAsync().Result하십시오. 그러면 올바르게 인코딩 된 쿼리 문자열이 제공되어 UriBuilder에 다시 할당 할 수 있습니다.
Triynko

실제로 ASP.NET이 '% uXXXX'인코딩을 사용하지 못하게하는 app.config / web.config 설정을 변경하는 경우에만 NamedValueCollection.ToString을 사용할 수 있습니다<add key="aspnet:DontUsePercentUUrlEncoding" value="true" /> . 이 동작에 의존하지 않으므로 이전 답변에서 알 수 있듯이 FormUrlEncodedContent 클래스를 사용하는 것이 좋습니다. stackoverflow.com/a/26744471/88409
Triynko

41

ASP.NET Core 프로젝트에서는 QueryHelpers 클래스를 사용할 수 있습니다.

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

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

2
이 프로세스를 사용하더라도 동일한 키에 대해 여러 값을 보낼 수는 없습니다. foo의 일부로 "bar"와 "bar2"를 보내려면 불가능합니다.
m0g

2
이것은 현대 앱에 대한 훌륭한 대답이며, 시나리오에서 간단하고 깨끗하게 작동합니다. 그러나 테스트되지 않은 이스케이프 메커니즘이 필요하지 않습니다.
Patrick Stalph

이 NuGet 패키지의 목표는 .NET 표준 2.0은 전체 .NET 프레임 워크 4.6.1+에서 사용할 수있는 수단
eddiewould

24

Flurl [disclosure : I 'm is the author] 을 확인하고 , 컴패니언 라이브러리 (선택 사항)를 사용하여 완전한 REST 클라이언트로 확장하는 유창한 URL 빌더 를 확인할 수 있습니다 .

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>();

체크 아웃 워드 프로세서 자세한 내용은. 전체 패키지는 NuGet에서 사용할 수 있습니다.

PM> Install-Package Flurl.Http

또는 독립형 URL 작성기 :

PM> Install-Package Flurl


2
Uri대신 자신의 수업으로 확장 하거나 시작 하지 string않겠습니까?
mpen

2
기술적으로 나는 자신의 Url수업으로 시작했다 . 위의 내용은 new Url("https://api.com").AppendPathSegment...개인적으로 키 입력 횟수가 적고 문서에서 표준화되어 있기 때문에 문자열 확장을 선호하지만 어느 쪽이든 할 수 있습니다.
Todd Menier

주제에서 벗어 났지만 정말 좋은 라이브러리입니다.이를 본 후에 사용하고 있습니다. IHttpClientFactory를 사용해 주셔서 감사합니다.
Ed S.

4

당신에 대한 참조를 포함하지 않는 경우 로스토프의 게시물과 같은 라인을 따라, System.Web당신의 프로젝트를, 당신은 사용할 수 있습니다 FormDataCollection에서 System.Net.Http.Formatting하고 다음과 같은 작업을 수행합니다

사용 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은 흥미롭고 영리한 솔루션을 제공했으며 여기 다른 옵션이 있습니다.

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();
    }
}

그리고 그것을 사용할 때, 당신은 이것을 할 수 있습니다 :

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

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

5
전체 쿼리 문자열이 아닌 for 루프 내부 를 인코딩 kvp.Key하고 kvp.Value별도로 인코딩하려고 합니다 (따라서 &=문자를 인코딩하지 마십시오 ).
Matthew

고마워 마이크. PCL 프로젝트에 있기 때문에 다른 제안 된 솔루션 (NameValueCollection 포함)이 작동하지 않았으므로 완벽한 대안이었습니다. 고객 측에서 일하는 다른 사람들을 위해 다음 server.UrlEncode과 같이 대체 할 수 있습니다.WebUtility.UrlEncode
BCA

2

또는 단순히 내 Uri 확장을 사용

암호

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());
}

용법

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

결과

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


27
URL 인코딩을 잊지 않았습니까?
Kugel

1
이것은 확장 기능을 사용하여 명확하고 유용한 도우미를 만드는 훌륭한 예입니다. 당신이 당신이 고체 위해 RESTClient를 구축에 당신의 방법에있어 허용 대답과 결합하는 경우
emran

2

내가 개발하고 있는 RFC 6570 URI 템플릿 라이브러리 는이 작업을 수행 할 수 있습니다. 모든 인코딩은 해당 RFC에 따라 처리됩니다. 이 글을 쓰는 시점에서 베타 릴리스를 사용할 수 있으며 안정적인 1.0 릴리스로 간주되지 않는 유일한 이유는 문서가 내 기대를 완전히 충족시키지 못하기 때문입니다 (문제 # 17 , # 18 , # 32 , # 43 참조 ).

쿼리 문자열 만 만들 수도 있습니다.

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

또는 완전한 URI를 작성할 수 있습니다.

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

이 시간을 재사용해야하므로 쿼리 문자열이 어떻게 구성되는지 추상화하는 데 도움이되는이 클래스를 생각해 냈습니다.

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;
        }
    }

}

다음과 같이 간단하게 사용할 수 있습니다.

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

그것은 URI를 반환합니다 : http://example.com/?foo=bar%3c%3e%26-baz&bar=second


1

taras.roshko의 답변에 설명 된 이중 인코딩 문제를 피하고 쿼리 매개 변수로 쉽게 작업 할 수 있도록 uriBuilder.Uri.ParseQueryString()대신 대신 사용할 수 있습니다 HttpUtility.ParseQueryString().


1

HttpUtility.ParseQueryString () 대신 UriBuilder.Uri.ParseQueryString ()을 사용하도록 수정 된 허용 된 응답의 좋은 부분 :

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();

참고 :이 참조가 필요 System.Net.Http을ParseQueryString()확장 메서드 내되지 않습니다 System.
Sunny Patel

0

"Darin Dimitrov"덕분에 이것이 확장 방법입니다.

 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

Dictionary를 QueryStringFormat으로 변환하는 확장 메서드를 만드는 것보다 더 나은 솔루션을 찾을 수 없습니다. Waleed AK가 제안한 솔루션도 좋습니다.

내 해결책을 따르십시오 :

확장 메소드를 작성하십시오.

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;
    }
}

그리고 그들:

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
이 솔루션에는 매개 변수의 적절한 URL 인코딩이 누락되어 '유효하지 않은'문자가 포함 된 값에는 작동하지 않습니다.
Xavier Poinas

답변을 업데이트하고 누락 된 인코딩 줄을 추가하십시오. 코드 라인 일뿐입니다!
Diego Mendes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.