web-dev-qa-db-ja.com

プロパティがWeb APIでシリアル化されないようにします

MVC 4 Web APIとasp.net Web Forms 4.0を使用してREST APIを構築しています。それはうまく機能しています:

[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オブジェクトが複雑すぎるため、異なるメソッドで異なるプロパティセットが必要です。実行時に、無視する各プロパティをマークする方が簡単です。

それを行う方法はありますか?

152
user1330271

ASP.NET Web 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形式をサポートしていません。したがって、アプリケーションがhas to XML形式をさらにサポートする(またはXMLのみをサポートする)場合、Json.Netを使用する代わりに、JSONとXMLの両方をサポートする[DataContract]を使用する必要があります。

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

詳細については、 公式記事 をご覧ください。

198
cuongle

Web APIドキュメントページ ASP.NET Web APIでのJSONおよびXMLシリアル化 によると、プロパティのシリアル化を明示的に防ぐには、Jsonシリアライザーに[JsonIgnore]を使用するか、デフォルトのXMLシリアライザーに[IgnoreDataMember]を使用できます。

ただし、テストで[IgnoreDataMember]がXML要求とJson要求の両方のシリアル化を妨げることに気づいたので、複数の属性でプロパティを修飾するのではなく、それを使用することをお勧めします。

106
Michael Mason

everythingをデフォルトでシリアル化する代わりに、「オプトイン」アプローチを取ることができます。このシナリオでは、指定したプロパティのみのシリアル化が許可されます。これを行うには、 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に依存することなく、モデルクラスをプロジェクト内で単独で使用できます。

26
CBono

これは私のために働いた:文字列配列型の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でオフにすることを忘れないでください。そうしないと、クライアントがjsonではなくxmlを要求した場合にAPIがブロックされたプロパティを返します。

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

目的を達成するための2つの方法を紹介します。

最初の方法:フィールドが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; }
}

2番目の方法:いくつかの複雑なシナリオと交渉している場合、特定のロジックに応じてそのフィールドのシリアル化をスキップするためにWeb 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という名前のフィールドはシリアル化されません。

16
foxhard

私はゲームに遅れていますが、匿名オブジェクトがトリックを行います:

[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;
}
15
Tim Hoolihan

IgnoreDataMemberプロパティを使用してみてください

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

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

AutoMapperを使用し、.Ignore()マッピングを使用してから、マッピングされたオブジェクトを送信できる場合があります

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

次を追加するだけで問題なく動作します。[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();
    }
2
Dannejaha

何らかの理由で[IgnoreDataMember]が常に機能するとは限らず、StackOverflowException(または同様の)を取得することがあります。したがって、代わりに(またはさらに)POSTObjectsingを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);
}

したがって、基本的にJObjectを渡し、オブジェクトの解析中に無限ループを引き起こすことがある組み込みのシリアライザーによって引き起こされる問題を回避するために受け取った後に変換します。

これが何らかの形で悪い考えであるという理由を誰かが知っているなら、私に知らせてください。

問題を引き起こすのは、EntityFrameworkクラスプロパティの次のコードであることに注意してください(2つのクラスが相互に参照している場合)。

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