Search code examples
c#entity-frameworkapiodata

Ignoring a JSON property in POST with oData


I'm trying to build an API with EntityFramework and OData v4.

Issue : I need some extra data, extraProperty, that are not in my DB to create a new Item, but oData won't recognize this as an Item if I add some data to my JSON object in my POST call.

I use EntityFrameWork so, according to this question I tried to use the data annotation [NotMapped] or .Ignore(t => t.extraProperty); in my model. But oData seems to ignore it.

All I get from this POST, with this extraProperty, is :

Does not support untyped value in non-open type.

Code

JSON I send in my POST call :

{
  "name": "John Doe",
  "extraProperty": "Random string"
}

$metadata :

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="MyApi.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityType Name="Items">
                <Key>
                    <PropertyRef Name="id" />
                </Key>
                <Property Name="id" Type="Edm.Int32" Nullable="false" />
                <Property Name="name" Type="Edm.String" Nullable="false" />                
            </EntityType>           
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

ODataConfig.cs

namespace MyApi.App_Start
{
    public class OdataConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Items>("Items");
            config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
            config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

Items.cs

[Table("Item.Items")]
public partial class Items
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Items(){}

    public int id { get; set; }

    public string name { get; set; }

    [NotMapped] // I already tried this, it's not working
    public string extraProperty{ get; set; }
 }

MyModel.cs

public partial class MyModel: DbContext
{
    public MyModel()
        : base("name=MyModel")
    {

        Database.SetInitializer<MyModel>(null);
    }

    public virtual DbSet<Items> Items{ get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // I also tried this but not working
        modelBuilder.Entity<Items>()
            .Ignore(e => e.extraProperty);
    }
}

MyController.cs

public class ItemsController : ODataController
{
    private MyModeldb = new MyModel();

    // POST: odata/Items 
    public async Task<IHttpActionResult> Post(Items items)
    {
        // items is always null when enterring here
        // and this condition is always triggered
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // Do some stuff with extraProperty here

        db.Items.Add(items);
        await db.SaveChangesAsync();

        return Created(items);
    }
}

Partial package.config

<package id="EntityFramework" version="6.2.0" targetFramework="net461" />
<package id="Microsoft.Data.Edm" version="5.8.3" targetFramework="net461" />
<package id="Microsoft.AspNet.OData" version="6.1.0" targetFramework="net45" />
<package id="Microsoft.Data.OData" version="5.8.3" targetFramework="net461" />
<package id="Microsoft.OData.Core" version="7.4.1" targetFramework="net45" />
<package id="Microsoft.OData.Edm" version="7.4.1" targetFramework="net45" />

I also thought to make an interceptor, to purge my json before post is called, but according to this question, Web API OData does not support query interceptor...

How can I deal with this error and avoid it ? I really need to process extraProperty in POST method, or at least, just before.


Solution

  • In your Items class, remove the [NotMapped] attribute for

    public string extraProperty{ get; set; }
    

    and leave the following code in your MyModel class

    modelBuilder.Entity<Items>()
                .Ignore(e => e.extraProperty);
    

    [NotMapped] attribute is telling OData to ignore the extraProperty when serialising and deserialising Items class. But since you want to use it in the POST request of ItemsController, you can't use the [NotMapped] attribute in this scenario, so the Model Binding takes place as you want it.