Programming

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

procodes 2020. 6. 2. 22:07
반응형

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


나머지 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객체가 너무 복잡하고 다른 방법으로 다른 속성 집합이 필요합니다. 런타임시 각 속성을 무시하도록 표시하기가 더 쉽습니다.

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


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

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


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

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


기본적으로 모든 것이 직렬화 되도록 하는 대신 "선택"접근 방식을 사용할 수 있습니다. 이 시나리오에서는 지정한 속성 만 직렬화 할 수 있습니다. 당신은이 작업을 수행 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에 의존하지 않고 프로젝트에 직접 살 수 있습니다.


이것은 나를 위해 일했습니다 : 문자열 배열 유형의 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);

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

첫 번째 방법 : 필드가 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; }
}

Second way: If you are negotiation with some complex scenarios then you could use the Web Api convention ("ShouldSerialize") in order to skip serialization of that field depending of some specific logic.

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 uses JSON.Net and it use reflection to serialization so when it has detected (for instance) the ShouldSerializeFieldX() method the field with name FieldX will not be serialized.


I'm late to the game, but an anonymous objects would do the trick:

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

Try using IgnoreDataMember property

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

Almost same as greatbear302's answer, but i create ContractResolver per request.

1) Create a custom 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) Use custom contract resolver in action

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>"),
            }
        }
    });
}

Edit:

It didn't work as expected(isolate resolver per request). I'll use anonymous objects.

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

You might be able to use AutoMapper and use the .Ignore() mapping and then send the mapped object

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

Works fine by just adding the: [IgnoreDataMember]

On top of the propertyp, like:

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

This works with ApiController. The code:

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

For some reason [IgnoreDataMember] does not always work for me, and I sometimes get StackOverflowException (or similar). So instead (or in addition) i've started using a pattern looking something like this when POSTing in Objects to my API:

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

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

So basically i pass in an JObject and convert it after it has been recieved to aviod problems caused by the built-in serializer that sometimes cause an infinite loop while parsing the objects.

If someone know a reason that this is in any way a bad idea, please let me know.

It may be worth noting that it is the following code for an EntityFramework Class-property that causes the problem (If two classes refer to each-other):

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

참고URL : https://stackoverflow.com/questions/11851207/prevent-property-from-being-serialized-in-web-api

반응형