Web API + OData - PATCH请求400错误

时间:2014-03-03 22:09:27

标签: kendo-ui odata http-status-code-400 asp.net-web-api2 httpverbs

我有一个KendoUI DataSource链接到WebApi 2 OData控制器,并且我遇到了更新操作的问题。我可以创建和删除就好了。

当我在进行任何更新后进行调用以将数据源同步到服务器时,我收到400错误:

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"The request is invalid."
    },"innererror":{
      "message":"patch : Invalid JSON. A token was not recognized in the JSON content.\r\n","type":"","stacktrace":""
    }
  }
}

在Visual Studio中进行调试显示补丁函数正在传递Id但不传递给Company对象。 Firebug显示PATCH请求如下所示:

models=%7B%22Id%22%3A1026%2C%22Title%22%3A%22Test+Company+test%22%7D

我有预感,服务器无法理解这一点。

模型很简单,我把控制器留给了VS为我生成的任何东西:

型号:

public class Company {
    public Company() { }

    public Company(Company company) {
        this.Id = company.Id;
        this.Title = company.Title;
        this.Projects = company.Projects;
    }

    public int Id { get; set; }
    public string Title { get; set; }

    public virtual ICollection<Project> Projects { get; set; }
}

控制器:

public class CompanyController : ODataController
{
    private ApplicationDbContext db = new ApplicationDbContext();

    // GET odata/Company
    [Queryable]
    public IQueryable<Company> GetCompany()
    {
        return db.Companies;
    }

    // GET odata/Company(5)
    [Queryable]
    public SingleResult<Company> GetCompany([FromODataUri] int key)
    {
        return SingleResult.Create(db.Companies.Where(company => company.Id == key));
    }

    // PUT odata/Company(5)
    public async Task<IHttpActionResult> Put([FromODataUri] int key, Company company)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (key != company.Id)
        {
            return BadRequest();
        }

        db.Entry(company).State = EntityState.Modified;

        try
        {
            await db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!CompanyExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return Updated(company);
    }

    // POST odata/Company
    public async Task<IHttpActionResult> Post(Company company)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Companies.Add(company);
        await db.SaveChangesAsync();

        return Created(company);
    }

    // PATCH odata/Company(5)
    [AcceptVerbs("PATCH", "MERGE")]
    public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Company> patch)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Company company = await db.Companies.FindAsync(key);
        if (company == null)
        {
            return NotFound();
        }

        patch.Patch(company);

        try
        {
            await db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!CompanyExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return Updated(company);
    }

    // DELETE odata/Company(5)
    public async Task<IHttpActionResult> Delete([FromODataUri] int key)
    {
        Company company = await db.Companies.FindAsync(key);
        if (company == null)
        {
            return NotFound();
        }

        db.Companies.Remove(company);
        await db.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }

    // GET odata/Company(5)/Projects
    [Queryable]
    public IQueryable<Project> GetProjects([FromODataUri] int key)
    {
        return db.Companies.Where(m => m.Id == key).SelectMany(m => m.Projects);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool CompanyExists(int key)
    {
        return db.Companies.Count(e => e.Id == key) > 0;
    }
}

最后,KendoUI,HTML / Javascript是这样的:

<h2>Company List</h2>

<div id="company-data">
    <div class="col-md-3 col-sm-5 col-xs-5">
        <div id="company-list" style="padding: 0px; height: 500px; overflow: auto" data-role="listview" data-template="list-template" data-bind="source: companies, events: {change: OnSelect}" data-selectable="true"></div>
        <div>
            <button class="btn btn-success btn-sm" id="btn-add-company"><span class="glyphicon glyphicon-plus"></span> Add</button>
            <button class="btn btn-danger btn-sm" id="btn-delete-company" data-bind="visible: hasSelection, click: deleteSelection"><span class="glyphicon glyphicon-remove"></span> Delete</button>
            <button class="btn btn-default btn-sm" id="btn-clear-company" data-bind="visible: hasSelection, click: clearSelection"><span class="glyphicon glyphicon-ban-circle"></span> Clear</button>
            <button class="btn btn-primary btn-sm btn-block" id="btn-save" data-bind="visible: hasChanges, click: saveChanges"><span class="glyphicon glyphicon-cloud-upload"></span> Save All</button>
        </div>
    </div>
    <div class="col-md-9 col-sm-7 col-xs-7" data-bind="visible: hasSelection">
        <label for="company-title">Title:</label><br />
        <input id="company-title" data-bind="value: selectedItem.Title" ><br />
    </div>
</div>

<script type="text/x-kendo-template" id="list-template">
    <div class="company" style="cursor: pointer">
        <span data-bind="text: Title"></span>
    </div>
</script>

<script>
    $(function () {
        var firstSync = true;
        var companyVM = new kendo.observable({
            // Data Source.
            companies: new kendo.data.DataSource({
                type: 'odata',
                transport: {
                    create: {
                        url: '/odata/Company',
                        dataType: 'json',
                        type: 'POST'
                    },
                    read: {
                        url: '/odata/Company',
                        dataType: 'json'
                    },
                    update: {
                        url: function (data) {
                            return '/odata/Company(' + data.Id + ')';
                        },
                        dataType: 'json',
                        type: 'PATCH'
                    },
                    destroy: {
                        url: function (data) {
                            return '/odata/Company(' + data.Id + ')';
                        },
                        dataType: 'json',
                        type: 'DELETE'
                    },
                    parameterMap: function (options, operation) {
                        if (operation !== "read" && options) {
                            console.log(operation + '*: ' + kendo.stringify(options));
                            return {
                                models: kendo.stringify(options)
                            };
                        }
                        console.log(operation + ': ' + kendo.stringify(options));
                        return options;
                    }
                },
                schema: {
                    data: function (data) {
                        return data['value'];
                    },
                    total: function (data) {
                        return data['odata.count'];
                    },
                    model: {
                        id: 'Id',
                        fields: {
                            Title: { type: 'string' }
                        }
                    }
                },
                change: function () {
                    // We don't want to fire the first time the data loads because that counts as changed.
                    if (!firstSync)
                        companyVM.set('hasChanges', true);
                    else
                        firstSync = false;
                }
            }),

            // Properties.
            selectedItem: null,
            hasSelection: function () {
                return this.get('selectedItem') != null;
            },
            hasChanges: false,

            // Functions.
            clearSelection: function() {
                this.set('selectedItem', null);
                $('#company-list').getKendoListView().clearSelection();
            },
            saveChanges: function() {
                this.companies.sync();
                this.set('hasChanges', false);
            },
            deleteSelection: function () {
                if (confirm('Warning, deletion is permanent! Are you sure you wish to delete this item?')) {
                    this.companies.remove(this.selectedItem);
                    this.set('hasChanges', true);
                    this.clearSelection();
                }
            },

            // Events.
            OnSelect: function (e) {
                var list = $(e.sender.element).getKendoListView();
                var row = list.select();
                var item = list.dataSource.getByUid(row.data('uid'));

                this.set('selectedItem', item);
            }
        });

        kendo.bind($('#company-data'), companyVM);
    });
</script>

1 个答案:

答案 0 :(得分:2)

在剑道支持论坛here上回答了问题。

解决方案是将parameterMap函数更改为:

parameterMap: function (data, operation) {
     return JSON.stringify(data);
}