📜 ⬆️ ⬇️

OData controllers in .NET MVC

The recently released ASP.NET and Web Tools 2012.2 Update declared partial support for the oData protocol in the ASP.NET Web API. I decided to try it myself, and share it with my colleagues. In the article I will describe how to use queries and CRUD operations using the oData protocol with several related data model objects. The Kendo UI framework is used as the front-end client.

Preface.

The article was written in order to consolidate the material studied on the study of new technology. Due to the complete lack of real experience in creating an application on the .NET MVC platform, I apologize in advance for any flaws. So…

Initial installation.

Download and install ASP.NET and Web Tools 2012.2 Update (if there is no desire, you can simply install the Microsoft ASP.NET Web API OData package in the created project). Create a new ASP.NET MVC 4 Web Application project and name it “ODataSample” . In the New ASP.NET MVC 4 Project dialog , select the Web API template.
Immediately put the KendoUIWeb package and make the necessary settings: you need to include the links to kendo.common.min.css, kendo.default.min.css, kendo.web.min.js in the _Layout.cshtml file.

Model.

Following the conventions, create a Category class in the Models folder and add the fields:
public class Category { public int ID { get; set; } [Required] [StringLength(50)] public string Name { get; set; } } 

In the same folder, create the ODataSampleContext context class file:
  public class ODataSampleContext : DbContext { public ODataSampleContext() : base("name=ODataSampleContext") { } public DbSet<Category> Categories { get; set; } } 

And we will definitely use the very useful functionality of the Entity Framework Migration - execute in the Package manager console:

As a result, we obtain a table in the database with the required fields. You could certainly not use Migration, but it is very convenient. I can’t even imagine how to work differently. But I'm distracted. Now we come to the most interesting.
')
OData controller.

We have not yet created a template for the OData controller, so select the “Empty API Controller” template and change the code to the following:
 public class CategoryController : EntitySetController<Category, int> { private ODataSampleContext db = new ODataSampleContext(); public override IQueryable<Category> Get() { return db.Categories; ; } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } 

In general, EntitySetController is inherited from ODataController , which in turn is from ApiController . It turns out that all this is a superstructure over WebApi. So in general, you can implement any protocol yourself, especially since there are all source codes for the ASP.NET CodePlex project. EntitySetController accepts two basic types: entity type and entity key type.
And the final touch: you need to make some changes to the WebApiConfig file:
 public static class WebApiConfig { public static void Register(HttpConfiguration config) { // ... config.EnableQuerySupport(); // ... ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(); modelBuilder.EntitySet<Category>("Category"); Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel(); config.Routes.MapODataRoute("ODataRoute", "odata", model); } } 

The EnableQuerySupport method includes OData query options (query options) for methods that return the IQueryable type. If there is no such need, then you can simply mark with the [Queryable] attribute those methods that you need. By the way, the following parameters are currently implemented: $ filter, $ inlinecount, $ orderby, $ skip, $ top ; not enough, but enough for basic purposes, for example, page-by-page partitioning on the server side (server-side paging). The rest of the code creates an Entity Data Model (EDM) model. The EntitySet method adds a set of entities to the EDM. The MapODataRoute method sets the URI and sets the endpoint (endpoint). This is all so that we can use the link: http: // localhost: 52864 / odata / Category . JSON result:
 { "odata.metadata":"http://localhost:52864/odata/$metadata#Category","value":[ { "ID":1,"Name":"Categoty1" },{ "ID":2,"Name":"Category2" } ] } 

To get a specific entry via the link http: // localhost: 52864 / odata / Category (1) , where the parameter is a key, you must override the GetEntityByKey method:
  //... protected override Category GetEntityByKey(int key) { return db.Categories.FirstOrDefault(c => c.ID == key); } //... 


Complicate the task.

Now create a Product model associated with Category .
 public class Product { public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } [Required] public int CategoryID { get; set; } public virtual Category Category { get; set; } } 

Do not forget to add a string to the context
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
  public DbSet Products { get; set; }    :  Add-Migration AddProduct, Update-Database .      . ,         Seed    Migrations/Configuration.cs   :  context.Products.AddOrUpdate(…) . 
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .
public DbSet Products { get; set; } : Add-Migration AddProduct, Update-Database . . , Seed Migrations/Configuration.cs : context.Products.AddOrUpdate(…) .
OData. , . OData . OData $expand , ; Data Tranfer Object (DTO):
public class ProductDTO { public ProductDTO() { } public ProductDTO(Product product) { ProductID = product.ProductID; Name = product.Name; Price = product.Price; CategoryID = product.CategoryID; CategoryName = product.Category.Name; } [Key] public int ProductID { get; set; } [Required] public string Name { get; set; } public decimal Price { get; set; } public int CategoryID { get; set; } public string CategoryName { get; set; } public Product ToEntity() { return new Product { ProductID = ProductID, Name = Name, Price = Price, CategoryID = CategoryID }; } }


CRUD
:
public class ProductController : EntitySetController<ProductDTO, int> { ODataSampleContext _context = new ODataSampleContext(); public override IQueryable<ProductDTO> Get() { return _context.Products.Include(p => p.Category) .Select(product => new ProductDTO { ProductID = product.ProductID, Name = product.Name, Price = product.Price, CategoryID = product.CategoryID, CategoryName = product.Category.Name, }); } protected override ProductDTO GetEntityByKey(int key) { return new ProductDTO(_context.Products.FirstOrDefault(p => p.ProductID == key)); } protected override void Dispose(bool disposing) { _context.Dispose(); base.Dispose(disposing); } }
– DTO. , "".
CRUD . :
protected override ProductDTO CreateEntity(ProductDTO entityDto) { if (ModelState.IsValid) { var entity = entityDto.ToEntity(); _context.Products.Add(entity); _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == entity.ProductID)); } else { HttpResponseMessage response = null; response = Request.CreateResponse(HttpStatusCode.BadRequest, new ODataError { ErrorCode = "ValidationError", Message = String.Join(";", ModelState.Values.First().Errors.Select(e => e.ErrorMessage).ToArray()) }); throw new HttpResponseException(response); } }
. HttpStatusCode.BadRequest ODataError .
, JSON. . HttpResponseException .
:
protected override ProductDTO UpdateEntity(int key, ProductDTO updateDto) { if (!_context.Products.Any(p => p.ProductID == key)) { throw new HttpResponseException(HttpStatusCode.NotFound); } var update = updateDto.ToEntity(); _context.Products.Attach(update); _context.Entry(update).State = System.Data.EntityState.Modified; _context.SaveChanges(); return new ProductDTO(_context.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == key)); } protected override ProductDTO PatchEntity(int key, Delta<ProductDTO> patch) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ProductDTO ProductDTO = new ProductDTO(product); patch.Patch(ProductDTO); _context.Products.Attach(ProductDTO.ToEntity()); _context.SaveChanges(); return new ProductDTO(product); } public override void Delete([FromODataUri] int key) { Product product = _context.Products.FirstOrDefault(p => p.ProductID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } _context.Products.Remove(product); _context.SaveChanges(); } protected override int GetKey(ProductDTO entity) { return entity.ProductID; }
, . PatchEntity, Delta, , .

– KendoUI Grid
HomeController :
public ActionResult Products() { return View(); }
View Products.cshtml View/Home , div Grid :
@{ ViewBag.Title = "Product"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="body"> <section class="featured"> <div class="content-wrapper"> <hgroup class="title"> <h1>Product</h1> </hgroup> <div id="Grid" style="height: 380px"></div> </div> </section> </div>
JavaScript:
Script <script> $(document).ready(function () { $("#Grid").kendoGrid({ columns: [ { field: "Name", title: "Product Name", width: "100px" }, { field: "CategoryName", title: "Category", width: "150px", editor: categoryDropDownEditor, template: "#=CategoryName#" }, { field: "Price", title: "Price", width: "100px" }, { command: "edit", title: "Edit", width: "110px" }, { command: "destroy", title: "Delete", width: "110px" }, ], pageable: true, pageSize: 5, sortable: true, filterable: true, editable: "popup", toolbar: ["create"], dataSource: { serverPaging: true, serverFiltering: true, serverSorting: true, pageSize: 5, type: "odata", schema: { data: function (response) { if (response.value !== undefined) return response.value; else{ delete response["odata.metadata"]; return response; } }, total: function (response) { return response['odata.count']; }, model: { id: "ProductID", fields: { ProductID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, Price: { nullable: false, type: "number" }, CategoryID: { type: "number",validation: { required: true }, editable: true}, CategoryName: { validation: { required: true }, editable: true }, } }, }, batch: false, error: error, transport: { create: { url: "/odata/Products", contentType: "application/json", type: "POST", }, read: { url: "/odata/Products", dataType: "json", contentType: "application/json", }, update: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, dataType: "json", contentType: "application/json", type: "PUT", headers: { Prefer: "return-content" } }, destroy: { url: function (record) { return "/odata/Products" + "(" + record.ProductID + ")"; }, contentType: "application/json", type: "DELETE" }, parametermap: function (data, operation) { console.log(data); if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } } } }); }); function categoryDropDownEditor(container, options) { $('<input data-bind="value:CategoryID"/>') .appendTo(container) .kendoDropDownList({ dataTextField: "Name", dataValueField: "ID", optionLabel: "--Select Value--", dataSource: { schema: { data: "value", total: function (response) { return response['odata.count']; }, model: { id: "ID", fields: { ID: { editable: false, type: "number" }, Name: { type: "string", nullable: false }, } }, }, type: "odata", serverFiltering: true, serverPaging: true, pageSize: 20, transport: { read: { url: "/odata/Categories", dataType: "json", contentType: "application/json" } }, parametermap: function (data, operation) { if (operation === "read") { var parammap = kendo.data.transports.odata.parametermap(data); return parammap; } return json.stringify(data); } }, }); } </script>

Kendo , . dataSource . schema ( data ), ( total ), . « total » , GET /odata/Products?%24inlinecount=allpages&%24top=5 5 . transport CRUD . headers: { Prefer: "return-content" } update , , . , . " CategoryName " editor: categoryDropDownEditor , .
:
script <script> function error(e) { if (e.errorThrown === "Bad Request") { var response = JSON.parse(e.xhr.responseText); console.log(response); if (response['odata.error'] != undefined) { alert(response['odata.error'].message.value) } } else { alert(e.status + ": " + e.errorThrown) } }; </script>

, ODataError , :
{ "odata.error":{ "code":"ValidationError","message":{ "lang":"en-US","value":" Name." } } }
.


image
REST CRUD , , , , , , . .net MVC, .
image
, :




PS . SPA (Single page application) . – . , , .

Source: https://habr.com/ru/post/180375/


All Articles