[C#] C #에서 URL에 대한 쿼리 문자열을 작성하는 방법은 무엇입니까?

코드에서 웹 리소스를 호출 할 때 일반적인 작업은 필요한 모든 매개 변수를 포함하도록 쿼리 문자열을 작성하는 것입니다. 로켓 과학은 &아니지만, 첫 번째 매개 변수가 아닌 경우 추가하고 매개 변수를 인코딩하는 등 몇 가지 멋진 세부 정보가 필요합니다 .

그것을하는 코드는 매우 간단하지만 약간 지루합니다.

StringBuilder SB = new StringBuilder();
if (NeedsToAddParameter A) 
{ 
  SB.Append("A="); SB.Append(HttpUtility.UrlEncode("TheValueOfA")); 
}

if (NeedsToAddParameter B) 
{
  if (SB.Length>0) SB.Append("&"); 
  SB.Append("B="); SB.Append(HttpUtility.UrlEncode("TheValueOfB")); }
}

이것은 더 우아하고 읽기 쉬운 유틸리티 클래스가 존재할 것으로 예상되는 일반적인 작업입니다. MSDN을 검색 할 때 하나를 찾지 못했습니다. 다음과 같은 질문이 나타납니다.

위의 작업을 수행하는 가장 우아한 방법은 무엇입니까?



답변

후드를 보면 QueryString 속성은 NameValueCollection입니다. 비슷한 작업을 수행하면 일반적으로 직렬화 및 역 직렬화에 관심이 있었으므로 NameValueCollection을 빌드 한 다음 전달하는 것이 좋습니다.

using System.Linq;
using System.Web;
using System.Collections.Specialized;

private string ToQueryString(NameValueCollection nvc)
{
    var array = (
        from key in nvc.AllKeys
        from value in nvc.GetValues(key)
            select string.Format(
                "{0}={1}",
                HttpUtility.UrlEncode(key),
                HttpUtility.UrlEncode(value))
        ).ToArray();
    return "?" + string.Join("&", array);
}

LINQ 에서도이 작업을 수행하는 매우 우아한 방법이 있다고 생각합니다.


답변

HttpValueCollection을 호출 하여 새 쓰기 가능한 인스턴스를 System.Web.HttpUtility.ParseQueryString(string.Empty)만든 다음 any로 사용할 수 있습니다 NameValueCollection. 원하는 값을 추가하면 ToString다음과 같이 컬렉션을 호출 하여 쿼리 문자열을 얻을 수 있습니다.

NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString.Add("key1", "value1");
queryString.Add("key2", "value2");

return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded

HttpValueCollection내부에 그래서 당신이 직접 인스턴스를 생성 할 수 없습니다. 그러나 일단 인스턴스를 얻으면 다른 인스턴스처럼 사용할 수 있습니다 NameValueCollection. 작업하고있는 실제 객체는이므로 HttpValueCollectionToString 메서드를 호출하면 재정의 된 메서드를 on에 호출 HttpValueCollection하여 컬렉션을 URL 인코딩 된 쿼리 문자열로 형식화합니다.

비슷한 문제에 대한 답변을 위해 SO와 웹을 검색 한 후에 이것은 내가 찾을 수있는 가장 간단한 솔루션입니다.

.NET 코어

.NET Core에서 작업하는 경우 Microsoft.AspNetCore.WebUtilities.QueryHelpers클래스를 사용 하면이를 단순화 할 수 있습니다 .

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers

샘플 코드 :

const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };

var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));


답변

Roy Tinker의 의견에서 영감을 받아 Uri 클래스에서 간단한 확장 메서드를 사용하여 코드를 간결하고 깨끗하게 유지했습니다.

using System.Web;

public static class HttpExtensions
{
    public static Uri AddQuery(this Uri uri, string name, string value)
    {
        var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

        httpValueCollection.Remove(name);
        httpValueCollection.Add(name, value);

        var ub = new UriBuilder(uri);
        ub.Query = httpValueCollection.ToString();

        return ub.Uri;
    }
}

용법:

Uri url = new Uri("http://localhost/rest/something/browse").
          AddQuery("page", "0").
          AddQuery("pageSize", "200");

편집-표준 호환 변형

여러 사람들이 지적했듯이 httpValueCollection.ToString()유니 코드 문자를 비표준 호환 방식으로 인코딩 합니다. 이것은 HttpUtility.UrlEncode사용되지 않는 HttpUtility.UrlEncodeUnicode메소드 대신 메소드를 호출하여 이러한 문자를 처리하는 동일한 확장 메소드의 변형입니다 .

using System.Web;

public static Uri AddQuery(this Uri uri, string name, string value)
{
    var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

    httpValueCollection.Remove(name);
    httpValueCollection.Add(name, value);

    var ub = new UriBuilder(uri);

    // this code block is taken from httpValueCollection.ToString() method
    // and modified so it encodes strings with HttpUtility.UrlEncode
    if (httpValueCollection.Count == 0)
        ub.Query = String.Empty;
    else
    {
        var sb = new StringBuilder();

        for (int i = 0; i < httpValueCollection.Count; i++)
        {
            string text = httpValueCollection.GetKey(i);
            {
                text = HttpUtility.UrlEncode(text);

                string val = (text != null) ? (text + "=") : string.Empty;
                string[] vals = httpValueCollection.GetValues(i);

                if (sb.Length > 0)
                    sb.Append('&');

                if (vals == null || vals.Length == 0)
                    sb.Append(val);
                else
                {
                    if (vals.Length == 1)
                    {
                        sb.Append(val);
                        sb.Append(HttpUtility.UrlEncode(vals[0]));
                    }
                    else
                    {
                        for (int j = 0; j < vals.Length; j++)
                        {
                            if (j > 0)
                                sb.Append('&');

                            sb.Append(val);
                            sb.Append(HttpUtility.UrlEncode(vals[j]));
                        }
                    }
                }
            }
        }

        ub.Query = sb.ToString();
    }

    return ub.Uri;
}


답변

얼마 전에 비슷한 질문 에 대답했습니다 . 기본적으로 가장 좋은 방법은 HttpValueCollectionASP.NET의 Request.QueryString속성이 실제로 클래스 인 클래스를 사용하는 것입니다. 불행히도 .NET 프레임 워크의 내부 클래스 입니다. Reflector를 사용하여 잡아서 (Utils 클래스에 배치) 할 수 있습니다. 이런 식으로 NameValueCollection과 같은 쿼리 문자열을 조작 할 수 있지만 모든 URL 인코딩 / 디코딩 문제가 해결됩니다.

HttpValueCollectionextends NameValueCollection이고 인코딩 된 쿼리 문자열 (앰퍼샌드 및 물음표 포함) 을 취하는 생성자가 있으며 ToString()나중에 기본 컬렉션에서 쿼리 문자열을 다시 작성 하는 메서드를 재정의합니다 .

예:

  var coll = new HttpValueCollection();

  coll["userId"] = "50";
  coll["paramA"] = "A";
  coll["paramB"] = "B";

  string query = coll.ToString(true); // true means use urlencode

  Console.WriteLine(query); // prints: userId=50&paramA=A&paramB=B


답변

다음은 동일한 키에 대해 여러 값을 지원하는 확장 방법 (이전 게시물의 개념 결합)으로서의 유창하고 복잡한 방법입니다. 개인적으로 선호하는 것은 다른 팀 구성원이 이와 같은 것을 발견 할 수 있도록 래퍼보다 확장 된 것입니다. 인코딩 방법, 스택 오버플로 (하나의에 대한 글을 많이 주위에 논란이 있다고 주 )와 (같은 MSDN 블로거 이 하나 ).

public static string ToQueryString(this NameValueCollection source)
{
    return String.Join("&", source.AllKeys
        .SelectMany(key => source.GetValues(key)
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))))
        .ToArray());
}

편집 : null 지원으로 특정 상황에 맞게 조정해야 할 수도 있습니다.

public static string ToQueryString(this NameValueCollection source, bool removeEmptyEntries)
{
    return source != null ? String.Join("&", source.AllKeys
        .Where(key => !removeEmptyEntries || source.GetValues(key)
            .Where(value => !String.IsNullOrEmpty(value))
            .Any())
        .SelectMany(key => source.GetValues(key)
            .Where(value => !removeEmptyEntries || !String.IsNullOrEmpty(value))
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), value != null ? HttpUtility.UrlEncode(value) : string.Empty)))
        .ToArray())
        : string.Empty;
}


답변

Flurl [disclosure : I ‘m is the author]은 익명 객체를 통해 쿼리 문자열 작성을 지원합니다 (다른 방법 중에서도).

var url = "http://www.some-api.com".SetQueryParams(new
{
    api_key = ConfigurationManager.AppSettings["SomeApiKey"],
    max_results = 20,
    q = "Don't worry, I'll get encoded!"
});

선택적 Flurl.Http 컴패니언 라이브러리를 사용하면 유동적 인 동일한 호출 체인에서 HTTP 호출을 수행하여 완전한 REST 클라이언트로 확장 할 수 있습니다.

T result = await "https://api.mysite.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { ap_key = "my-key" })
    .WithOAuthBearerToken("MyToken")
    .PostJsonAsync(new { first_name = firstName, last_name = lastName })
    .ReceiveJson<T>();

전체 패키지는 NuGet에서 사용할 수 있습니다.

PM> Install-Package Flurl.Http

또는 독립형 URL 작성기 :

PM> Install-Package Flurl


답변

여기 내 늦은 입장입니다. 나는 여러 가지 이유로 다른 사람들을 좋아하지 않아서 내 자신을 썼습니다.

이 버전의 특징 :

  • StringBuilder 만 사용하십시오. ToArray () 호출 또는 다른 확장 메소드가 없습니다. 그것은 다른 응답들만큼 아름답게 보이지는 않지만, 이것이 핵심 기능이라고 생각하므로 비 효율성을 숨기는 “유창한”, “한 줄짜리”코드를 갖는 것보다 효율성이 더 중요합니다.

  • 키당 여러 값을 처리합니다. (나 자신이 필요하지는 않지만 Mauricio를 침묵시키기 위해)

    public string ToQueryString(NameValueCollection nvc)
    {
        StringBuilder sb = new StringBuilder("?");
    
        bool first = true;
    
        foreach (string key in nvc.AllKeys)
        {
            foreach (string value in nvc.GetValues(key))
            {
                if (!first)
                {
                    sb.Append("&");
                }
    
                sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
    
                first = false;
            }
        }
    
        return sb.ToString();
    }

사용법 예

        var queryParams = new NameValueCollection()
        {
            { "x", "1" },
            { "y", "2" },
            { "foo", "bar" },
            { "foo", "baz" },
            { "special chars", "? = &" },
        };

        string url = "http://example.com/stuff" + ToQueryString(queryParams);

        Console.WriteLine(url);

산출

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26