웹 API에서 속성이 직렬화되지 않도록 방지


174

나머지 API를 빌드하기 위해 MVC 4 웹 API 및 asp.net 웹 양식 4.0을 사용하고 있습니다. 잘 작동합니다.

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = somethings });

    return httpResponseMessage;
}

이제 일부 속성이 직렬화되는 것을 방지해야합니다. 나는 목록에서 LINQ를 사용할 수 있고 필요한 속성 만 얻을 수 있으며 일반적으로 좋은 접근 방법이지만 현재 시나리오에서는 something객체가 너무 복잡하고 다른 방법으로 다른 속성 집합이 필요합니다. 런타임시 각 속성을 무시하도록 표시하기가 더 쉽습니다.

그렇게 할 방법이 있습니까?


속성에 ScriptIgnore를 추가 할 수 있습니다. 이 질문보기 stackoverflow.com/questions/10169648/…
atbebtg

답변:


232

ASP.NET 웹 API는 Json.Net기본 포맷터로 사용 하므로 응용 프로그램에서 JSON 만 데이터 형식으로 사용 [JsonIgnore]하는 경우 직렬화 속성을 무시 하는 데 사용할 수 있습니다 .

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonIgnore]
    public List<Something> Somethings { get; set; }
}

그러나이 방법은 XML 형식을 지원하지 않습니다. 그래서, 경우에 귀하의 응용 프로그램 에이 사용하는 대신, 지원 XML 포맷 이상 (또는 만 지원 XML) Json.Net사용한다, [DataContract]JSON과 XML을 모두 지원한다 :

[DataContract]
public class Foo
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }

    //Ignore by default
    public List<Something> Somethings { get; set; }
}

자세한 내용은 공식 기사를 참조하십시오 .


jsonignore를 사용하여 런타임에 속성을 추가하고 제거하는 방법을 찾아야한다고 생각합니다.
user1330271

참처럼 작동했습니다! 감사합니다 :)
Paulo Rodrigues

JsonIgnore 속성이 XML 응답으로 지원되지 않는 것이 왜 슬프습니까?
Mukus

데이터 계약은 훌륭한 솔루션입니다. 깨끗한 REST API를 제공합니다. 데이터를 no-sql에 저장하는 동시에 객체가 json으로 저장되어 있음에도 불구하고 무시 된 속성이 유지됩니다.
FrankyHollywood

1
@FedorSteeman JsonIgnore의 네임 스페이스는 Newtonsoft.Json이며 JSON.Net-nuget 패키지가 필요합니다. 반면에 DataContract 및 DataMember-속성에는 System.Runtime.Serialization-namespace (없으면 참조)가 필요합니다
Esko

113

ASP.NET 웹 API 의 웹 API 문서 페이지 JSON 및 XML 직렬화에[JsonIgnore] 따르면 Json 직렬 변환기 또는 [IgnoreDataMember]기본 XML 직렬 변환기에 사용할 수있는 특성의 직렬화를 명시 적으로 방지 할 수 있습니다 .

그러나 테스트에서 [IgnoreDataMember]XML과 Json 요청 모두의 직렬화를 방지 한다는 것을 알았 으므로 여러 속성으로 속성을 장식하는 대신 사용하는 것이 좋습니다.


2
이것이 더 나은 대답입니다. 하나의 속성으로 XML과 JSON을 다룹니다.
Oliver

17
슬프게도 [IgnoreDataMember]지연로드 된 EF 6 프록시 개체 (가상 속성)에서는 작동하지 않는 것 같습니다. [DataContract][DataMember]그러나 않습니다.
Nick

32

기본적으로 모든 것이 직렬화 되도록 하는 대신 "선택"접근 방식을 사용할 수 있습니다. 이 시나리오에서는 지정한 속성 만 직렬화 할 수 있습니다. 당신은이 작업을 수행 DataContractAttribute하고 DataMemberAttribute에서 발견 들은 System.Runtime.Serialization의 네임 스페이스.

DataContactAttribute클래스에 적용되고는 DataMemberAttribute당신이 직렬화 할 각 멤버에 적용된다 :

[DataContract]
public class MyClass {

  [DataMember]
  public int Id { get; set;} // Serialized

  [DataMember]
  public string Name { get; set; } // Serialized

  public string DontExposeMe { get; set; } // Will not be serialized
}

감히 나는 이것이 직렬화를 통해 무엇을 할 것인지 아닌지를 분명하게 결정하도록 강요하기 때문에 더 나은 접근 방법이라고 말합니다. 또한 JSON.net에 의존하지 않고 모델 클래스를 JSON.net에 의존하지 않고 모델 클래스가 프로젝트에 살 수 있습니다.


2
상속 된 멤버를 숨기기 위해 .Net Core와 함께 사용 가능한 유일한 방법입니다. XML 및 Json 직렬화 모두에서 작동합니다. Kudos
Piou 2016 년

동일한 기능이 필요하지만 속성이 포함 또는 제외되는 API 방법에 따라 속성이 포함되거나 제외됩니다. 즉, 다른 API 호출에 다른 데이터가 필요합니다. 어떤 제안
Nithin Chandran

이것은 훌륭하게 작동하지만 내 주요 문제는 이러한 구성이 EF Core의 모든 dbcontext 스캐 폴드와 함께 사라진다는 것입니다. 누구도 해결할 수 있습니까? 이러한 속성이 부분 클래스에 있거나 프로그래밍 방식으로 설정 될 수 있습니까?
Naner

20

이것은 나를 위해 일했습니다 : 문자열 배열 유형의 AllowList라는 공용 속성이있는 사용자 지정 계약 해결 프로그램을 만드십시오. 조치에서 조치를 리턴해야하는 항목에 따라 해당 특성을 수정하십시오.

1. 사용자 지정 계약 해결 프로그램을 만듭니다.

public class PublicDomainJsonContractResolverOptIn : DefaultContractResolver
{
    public string[] AllowList { get; set; }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);

        properties = properties.Where(p => AllowList.Contains(p.PropertyName)).ToList();
        return properties;
    }
}

2. 실제 사용자 지정 계약 해결 프로그램 사용

[HttpGet]
public BinaryImage Single(int key)
{
    //limit properties that are sent on wire for this request specifically
    var contractResolver = Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver as PublicDomainJsonContractResolverOptIn;
    if (contractResolver != null)
        contractResolver.AllowList = new string[] { "Id", "Bytes", "MimeType", "Width", "Height" };

    BinaryImage image = new BinaryImage { Id = 1 };
    //etc. etc.
    return image;
}

이 접근 방식을 통해 클래스 정의를 수정하는 대신 특정 요청에 대해 허용 / 금지 할 수있었습니다. XML 직렬화가 필요하지 않은 App_Start\WebApiConfig.cs경우 클라이언트 에서 XML 직렬화를 해제해야 합니다. 그렇지 않으면 클라이언트가 json 대신 xml을 요청하는 경우 API에서 차단 된 속성을 반환합니다.

//remove xml serialization
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);

최신 버전에서 변경된 것이 있었지만 작동하지 못했습니다. 리졸버를 수정할 때 'as'대신 'new'를 수행하여 작동시킬 수 있습니다. 어떤 이유로 JsonContractResolver 유형이 호환되지 않습니다. 새로운 일을 할 때의 문제는 하나를 대신하는 대신 모든 것을 덮어 씁니다.
Kalel Wade

다음과 같이 MediaTypeFormatter를받는 Request.CreateResponse () 메서드를 사용하여이 작업을 수행했습니다. var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn {AllowList = new string [] { "Id", " 바이트 ","MimeType ","너비 ","높이 "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul

XML 응답에서 차단 된 속성을 무시하려면 어떻게해야합니까?
Carlos P

데이터 계약 확인자가 요청 당 한 번 할당되지 않으면 스레드 안전이 아닙니다. 나는 이것이 시작 클래스에서 한 번 할당 된 것으로 생각합니다.
Sprague

2
더 나쁜 것은 ive 테스트하고 createproperties 호출은 계약 해결 프로그램에 의해 캐시됩니다. 이 답변은 순진하고 최악의 경우 위험합니다.
Sprague

19

나는 당신이 원하는 것을 성취하는 두 가지 방법을 보여줄 것입니다 :

첫 번째 방법 : 필드가 null 인 경우 해당 필드의 직렬화를 건너 뛰려면 JsonProperty 속성으로 필드를 장식하십시오.

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<Something> Somethings { get; set; }
}

두 번째 방법 : 복잡한 시나리오와 협상하는 경우 특정 논리에 따라 해당 필드의 직렬화를 건너 뛰기 위해 웹 API 규칙 ( "ShouldSerialize")을 사용할 수 있습니다.

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Something> Somethings { get; set; }

    public bool ShouldSerializeSomethings() {
         var resultOfSomeLogic = false;
         return resultOfSomeLogic; 
    }
}

WebApi는 JSON.Net을 사용하고 직렬화에 반영을 사용하므로, 예를 들어 ShouldSerializeFieldX () 메소드를 감지하면 이름이 FieldX 인 필드가 직렬화되지 않습니다.


그것은 웹 API에 의해 수행되지 않으며, 웹 API는 직렬화를 위해 기본적으로 Json.NET을 사용합니다. 이 프로세스는 웹 API가 아닌 Json.NET에 의해 수행됩니다
Hamid Pourjam

1
두 번째 솔루션은 일부 필드를 숨기려고 DTO를 다시 작성할 필요없이 Domain 개체 기술을 무시할 수 있기 때문에 좋습니다.
Raffaeu

17

나는 게임에 늦었지만 익명의 물건이 트릭을 할 것입니다.

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    var returnObjects = somethings.Select(x => new {
        Id = x.Id,
        OtherField = x.OtherField
    });

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = returnObjects });

    return httpResponseMessage;
}

11

IgnoreDataMember속성을 사용해보십시오

public class Foo
    {
        [IgnoreDataMember]
        public int Id { get; set; }
        public string Name { get; set; }
    }

5

greatbear302의 답변과 거의 동일하지만 요청마다 ContractResolver를 만듭니다.

1) 사용자 정의 ContractResolver 작성

public class MyJsonContractResolver : DefaultContractResolver
{
    public List<Tuple<string, string>> ExcludeProperties { get; set; }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (ExcludeProperties?.FirstOrDefault(
            s => s.Item2 == member.Name && s.Item1 == member.DeclaringType.Name) != null)
        {
            property.ShouldSerialize = instance => { return false; };
        }

        return property;
    }
}

2) 실제 사용자 지정 계약 해결 프로그램 사용

public async Task<IActionResult> Sites()
{
    var items = await db.Sites.GetManyAsync();

    return Json(items.ToList(), new JsonSerializerSettings
    {
        ContractResolver = new MyJsonContractResolver()
        {
            ExcludeProperties = new List<Tuple<string, string>>
            {
                Tuple.Create("Site", "Name"),
                Tuple.Create("<TypeName>", "<MemberName>"),
            }
        }
    });
}

편집하다:

예상대로 작동하지 않았습니다 (요청 당 리졸버 분리). 익명 개체를 사용하겠습니다.

public async Task<IActionResult> Sites()
{
    var items = await db.Sites.GetManyAsync();

    return Json(items.Select(s => new
    {
        s.ID,
        s.DisplayName,
        s.Url,
        UrlAlias = s.Url,
        NestedItems = s.NestedItems.Select(ni => new
        {
            ni.Name,
            ni.OrdeIndex,
            ni.Enabled,
        }),
    }));
}

4

AutoMapper를 사용하고 .Ignore()매핑을 사용한 다음 매핑 된 개체를 보낼 수 있습니다.

CreateMap<Foo, Foo>().ForMember(x => x.Bar, opt => opt.Ignore());

3

다음을 추가하여 정상적으로 작동합니다. [IgnoreDataMember]를 .

propertyp 위에 다음과 같이 :

public class UserSettingsModel
{
    public string UserName { get; set; }
    [IgnoreDataMember]
    public DateTime Created { get; set; }
}

이것은 ApiController와 함께 작동합니다. 코드:

[Route("api/Context/UserSettings")]
    [HttpGet, HttpPost]
    public UserSettingsModel UserSettings()
    {
        return _contextService.GetUserSettings();
    }

또한 더 나은 솔루션은 "백엔드"모델에서 뷰 모델을 분리하는 것이므로이 선언을 건너 뛸 수 있습니다. 나는 그 상황에서 종종 나 자신을 더 잘 찾습니다.
Dannejaha '

0

어떤 이유로 든 [IgnoreDataMember]항상 나를 위해 작동하지 않으며, 때로는 StackOverflowException(또는 비슷한) 얻을 수 있습니다. 따라서 대신 (또는 추가로) API POSTObjects연결할 때 다음과 같은 패턴을 사용하기 시작했습니다 .

[Route("api/myroute")]
[AcceptVerbs("POST")]
public IHttpActionResult PostMyObject(JObject myObject)
{
    MyObject myObjectConverted = myObject.ToObject<MyObject>();

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

그래서 기본적으로 나는 전달 JObject하고 내장 직렬 변환기로 인해 발생하는 심각한 문제로 수신 된 후 변환하여 때로는 객체를 구문 분석하는 동안 무한 루프를 발생시킵니다.

누군가 이것이 어떤 식 으로든 나쁜 생각을하는 이유를 알고 있다면 알려주십시오.

문제를 일으키는 EntityFramework 클래스 속성에 대한 다음 코드임을 주목할 가치가 있습니다 (두 클래스가 서로 참조하는 경우).

[Serializable]
public partial class MyObject
{
   [IgnoreDataMember]
   public MyOtherObject MyOtherObject => MyOtherObject.GetById(MyOtherObjectId);
}

[Serializable]
public partial class MyOtherObject
{
   [IgnoreDataMember]
   public List<MyObject> MyObjects => MyObject.GetByMyOtherObjectId(Id);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.