Question

I'm trying to find an example of a Kendo Grid MVC server wrapper using a Web API 2 controller and not having much luck. Is there a way to use those server wrappers with the controller generated by VS using "Add" -> "Controller" -> "Web API 2 Controller with actions, using Entity Framework?" Does using async controller actions affect this?

I've looked at the tutorials at Telerik (http://docs.telerik.com/kendo-ui/tutorials/asp.net/asp-net-hello-services) and at the sample project (http://www.telerik.com/support/code-library/binding-to-a-web-apicontroller-6cdc432b8326), but neither of those are using the new Web API 2 controllers that VS can automatically generate.

I'd prefer to be able to use the VS generated controllers, but my skills are apparently not up to adapting the existing examples to them (this is my first project with MVC/Web API). Has anyone else done this or should I just write controllers like in those two-year-old examples?

ETA: Just to include the starting point I've currently got:

I created models using EF Code First from database. The simple one I'm using to test this with is this one:

namespace TestProject.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;

    [Table("TP.Industry")]
    public partial class Industry
    {
        public Industry()
        {
            Companies = new HashSet<Company>();
        }

        public int IndustryId { get; set; }

        public int IndustryCode { get; set; }

        [Required]
        [StringLength(150)]
        public string IndustryName { get; set; }

        public virtual ICollection<Company> Companies { get; set; }
    }
}

I then created controllers using the "Web API 2 Controller with actions, using Entity Framework" option and checked "Use async controller actions" to get the following controller for that model:

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using TestProject.Models;

namespace TestProject.Controllers
{
    public class IndustryController : ApiController
    {
        private TestProjectContext db = new TestProjectContext();

        // GET api/Industry
        public IQueryable<Industry> GetIndustries()
        {
            return db.Industries;
        }

        // GET api/Industry/5
        [ResponseType(typeof(Industry))]
        public async Task<IHttpActionResult> GetIndustry(int id)
        {
            Industry industry = await db.Industries.FindAsync(id);
            if (industry == null)
            {
                return NotFound();
            }

            return Ok(industry);
        }

        // PUT api/Industry/5
        public async Task<IHttpActionResult> PutIndustry(int id, Industry industry)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != industry.IndustryId)
            {
                return BadRequest();
            }

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

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

            return StatusCode(HttpStatusCode.NoContent);
        }

        // POST api/Industry
        [ResponseType(typeof(Industry))]
        public async Task<IHttpActionResult> PostIndustry(Industry industry)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Industries.Add(industry);
            await db.SaveChangesAsync();

            return CreatedAtRoute("DefaultApi", new { id = industry.IndustryId }, industry);
        }

        // DELETE api/Industry/5
        [ResponseType(typeof(Industry))]
        public async Task<IHttpActionResult> DeleteIndustry(int id)
        {
            Industry industry = await db.Industries.FindAsync(id);
            if (industry == null)
            {
                return NotFound();
            }

            db.Industries.Remove(industry);
            await db.SaveChangesAsync();

            return Ok(industry);
        }

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

        private bool IndustryExists(int id)
        {
            return db.Industries.Count(e => e.IndustryId == id) > 0;
        }
    }
}

Finally, I put the following Kendo UI MVC wrapper code in my view for the grid:

@(Html.Kendo().Grid<TestProject.Models.Industry>()
    .Name("Grid")
    .Columns(columns =>
    {
        columns.Bound(c => c.IndustryId);
        columns.Bound(c => c.IndustryCode);
        columns.Bound(c => c.IndustryName);
        columns.Command(c =>
        {
            c.Edit();
            c.Destroy();
        });
    })
    .ToolBar(tools =>
    {
        tools.Create();
    })
    .Sortable()
    .Pageable(pageable => pageable
        .Refresh(true)
        .PageSizes(true)
        .ButtonCount(5))
    .Filterable()
    .DataSource(dataSource => dataSource
        .Ajax()
        .Model(model =>
        {
            model.Id(c => c.IndustryId);
        })
        .Read(read => read.Url("../api/Industry").Type(HttpVerbs.Get))
        .Create(create => create.Url("../api/Industry").Type(HttpVerbs.Post))
        .Update(update => update.Url("../api/Industry").Type(HttpVerbs.Put))
        .Destroy(destroy => destroy.Url("../api/Industry").Type(HttpVerbs.Delete))
    )
)

<script>

    $(function () {
        var grid = $("#Grid").data("kendoGrid");

        // WebAPI needs the ID of the entity to be part of the URL e.g. PUT /api/Product/80
        grid.dataSource.transport.options.update.url = function (data) {
            return "api/Industry/" + data.IndustryId;
        }

        // WebAPI needs the ID of the entity to be part of the URL e.g. DELETE /api/Product/80
        grid.dataSource.transport.options.destroy.url = function (data) {
            return "api/Industry/" + data.IndustryId;
        }
    });

</script>

The grid returns no data and request for the api returns this 500 Internal Server Error:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
        The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
    </ExceptionMessage>
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <StackTrace/>
    <InnerException>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
        Type 'System.Data.Entity.DynamicProxies.Industry_5BBD811C8CEC2A7DB96D23BD05DB137D072FDCC62C2E0039D219F269651E59AF' with data contract name 'Industry_5BBD811C8CEC2A7DB96D23BD05DB137D072FDCC62C2E0039D219F269651E59AF:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
    </ExceptionMessage>
    <ExceptionType>
        System.Runtime.Serialization.SerializationException
    </ExceptionType>
    <StackTrace>
        at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph) at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
    </StackTrace>
    </InnerException>
</Error>

In fact, if I just scaffold a view on that model, like this:

@model IEnumerable<TestProject.Models.Industry>

@{
    ViewBag.Title = "Industries";
}

<h2>Industries</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.IndustryCode)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.IndustryName)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.IndustryCode)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.IndustryName)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.IndustryId }) |
            @Html.ActionLink("Details", "Details", new { id=item.IndustryId }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.IndustryId })
        </td>
    </tr>
}

</table>

The "@foreach (var item in Model) {" line generates a "NullReferenceException: Object reference not set to an instance of an object." error.

It seems like even if the Kendo part wasn't right, at the very least all of the VS generated code should work correctly and it doesn't seem to be.

Was it helpful?

Solution

So, based on my research so far, there just don't seem to be any examples of using the Kendo MVC wrappers with Web API 2 controllers with async actions.

However, there now seems to be a Web API Editing section in the Kendo UI Getting Started docs (that I swear wasn't there before) that has a more up to date example that doesn't rely on adding the DataSourceRequestModelBinder.cs file to the project.

The controller setup is basically like the previous examples, but the .DataSource on the grid wrapper now looks like this:

.DataSource(dataSource => dataSource
    .WebApi()
    .Model(model =>
    {
        model.Id(i => i.IndustryId);
        model.Field(i => i.IndustryId).Editable(false);
    })
    .Create(create => create.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "Industries" })))
    .Read(read => read.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "Industries" })))
    .Update(update => update.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "Industries", id = "{0}" })))
    .Destroy(destroy => destroy.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "Industries", id = "{0}" })))
)

That .WebApi() method just wasn't showing up in any of my searches. This seems to work though, so I'm going to run with it. Thanks, all!

OTHER TIPS

Here you can find a repository with more examples regarding the MVC technology. There should be an example that you are searching for.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top