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


174

MVC 4 Web APIとasp.net Webフォーム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オブジェクトが複雑すぎて、さまざまなメソッドでさまざまなプロパティのセットが必要なので、実行時に、無視する各プロパティをマークするのが簡単です。

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


プロパティにScriptIgnoreを追加できます。この質問の閲覧stackoverflow.com/questions/10169648/...を
atbebtg

回答:


231

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形式はサポートされません。したがって、アプリケーション 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

チャームのように働いた!ありがとう:)
パウロロドリゲス

JsonIgnore属性がXML応答でサポートされていないのはなぜ悲しいのですか?
ムクス2016年

Datacontractは優れたソリューションです。それは私にきれいなREST APIを与えます。データを非SQLで保存すると同時に、オブジェクトがjsonとして保存されているにもかかわらず、無視されたプロパティが保持されます。
FrankyHollywood 2018年

1
@FedorSteeman JsonIgnoreの名前空間はNewtonsoft.Jsonであり、JSON.Net-nugetパッケージが必要です。一方、DataContractおよびDataMember -attributesにはSystem.Runtime.Serialization-namespaceが必要です(欠落している場合は参照)
Esko

113

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

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


2
これがより良い答えです。XMLとJSONを1つの属性でカバーしています。
オリバー

17
悲しいことに[IgnoreDataMember]、遅延ロードされたEF 6プロキシオブジェクト(仮想プロパティ)では機能しないようです。 しかし[DataContract][DataMember]そうです。
Nick、

32

デフォルトですべてをシリアル化させる代わりに、「オプトイン」アプローチをとることができます。このシナリオでは、指定したプロパティのみをシリアル化できます。これは、System.Runtime.Serialization名前空間にあるDataContractAttributeand DataMemberAttributeで行います。

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の両方のシリアル化で機能します。賞賛
Piou

同じ機能が必要ですが、プロパティが含まれるか除外されるかは、呼び出される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シリアル化が必要ない場合は、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);

新しいバージョンでは何か変更があったに違いありませんが、これを機能させることができませんでした。リゾルバーを変更するときに「as」の代わりに「new」を実行することで機能させることができます。JsonContractResolverタイプは、何らかの理由で互換性がありません。新規作成の問題は、1つだけでなくすべてを上書きすることです。
Kalel Wade、2014

次のように、MediaTypeFormatterを受け取るRequest.CreateResponse()メソッドを使用してこれを機能させることができました。varjsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn {AllowList = new string [] {"Id"、 "バイト」、「MimeType」、「幅」、「高さ」}}}}; return Request.CreateResponse(HttpStatusCode.OK、image、jsonMediaTypeFormatter);
ポール

ブロックされたプロパティもXML応答で無視したい場合はどうなりますか?
Carlos P

データコントラクトリゾルバーがリクエストごとに1回割り当てられない限り、これはスレッドセーフではありません。これは、スタートアップクラスで一度割り当てられると思います。
スプレーグ

2
さらに悪いことに、これをテストすると、createproperties呼び出しはコントラクトリゾルバーによってキャッシュされます。この回答はせいぜいナイーブであり、最悪の場合は危険です。
スプレーグ

19

私はあなたが望むものを達成するための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という名前のフィールドはシリアル化されません。


これはweb apiでは行われません。webapiはデフォルトでシリアル化にJson.NETを使用します。このプロセスは、Web APIではなくJson.NETによって行われます
Hamid Pourjam

1
2番目のソリューションは、一部のフィールドを非表示にするためだけにDTOを書き換える必要なく、ドメインオブジェクトテクノロジーにとらわれないようにすることができるため、優れています。
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 に入力POSTするときに、次のようなパターンを使用し始めましたObjects

[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組み込みのシリアライザーによって引き起こされるaviodの問題を受け取った後で、オブジェクトの解析中に無限ループを引き起こすことがある問題に変換します。

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

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.