最近リリースされたASP.NETおよび
Web Tools 2012.2 Updateは、ASP.NET Web APIのoDataプロトコルの部分的なサポートを主張しています。 私は自分で試して、同僚と共有することにしました。 この記事では、oDataプロトコルといくつかの関連データモデルオブジェクトを使用して、クエリとCRUD操作を使用する方法を説明します。 フロントエンドクライアントとして、Kendo UIフレームワークが使用されます。
まえがき
この記事は、新技術の研究の対象となる資料を統合するために書かれました。 .NET MVCプラットフォームでアプリケーションを作成した実際の経験がまったくないため、欠陥の可能性について事前に謝罪します。 だから...
初期インストール。
ASP.NETおよびWeb Tools 2012.2 Updateをダウンロードしてインストールし
ます (必要ない場合は、作成したプロジェクトに
Microsoft ASP.NET Web API ODataパッケージをインストールするだけです)。 新しい
ASP.NET MVC 4 Webアプリケーションプロジェクトを作成し、
「ODataSample」と呼びます。 [
新しいASP.NET MVC 4プロジェクト ]ダイアログ
で 、
Web APIテンプレートを選択し
ます 。
すぐ
にKendoUIWebパッケージを
インストールし 、必要な設定を
行います
。_Layout.cshtmlファイルに
、kendo.common.min.css、kendo.default.min.css、kendo.web.min.jsへのリンクを含める必要があります
。モデル。
規則に従って、
Modelsフォルダーに
Categoryクラスを作成し、フィールドを追加します。
public class Category { public int ID { get; set; } [Required] [StringLength(50)] public string Name { get; set; } }
同じフォルダーで、
ODataSampleContextコンテキストクラスファイルを作成します。
public class ODataSampleContext : DbContext { public ODataSampleContext() : base("name=ODataSampleContext") { } public DbSet<Category> Categories { get; set; } }
そして、Entity Framework Migrationの非常に便利な機能を間違いなく使用します。パッケージマネージャーコンソールで実行します。
- 移行を有効にする
- 追加移行の初期化
- データベースを更新する
その結果、必要なフィールドを持つデータベースのテーブルを取得します。 もちろん、移行を使用しないこともできましたが、非常に便利です。 どうすれば違う仕事ができるか想像もできません。 しかし、私は何かに気を取られています。 次に、最も興味深いものに移ります。
ODataコントローラー。
ODataコントローラーのテンプレートはまだ作成していないため、
「Empty API Controller」テンプレートを選択し、コードを次のように変更します。
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); } }
一般に、
EntitySetControllerは ODataControllerから継承され、
ODataControllerはApiControllerから継承されます。 これらはすべてWebApiのアドオンであることがわかりました。 したがって、一般的には、特にASP.NET CodePlexプロジェクトのすべてのソースコードがあるため、自分でプロトコルを実装できます。
EntitySetControllerは、エンティティタイプとエンティティキータイプの2つの主なタイプを受け入れます。
最後の
仕上げ :
WebApiConfigファイルにいくつかの変更を加える必要があります。
public static class WebApiConfig { public static void Register(HttpConfiguration config) {
EnableQuerySupportメソッドには、
IQueryable型を返すメソッドのODataクエリオプションが含まれています。 これが不要な場合は、必要なメソッドを
[Queryable]属性で簡単にマークでき
ます 。 ところで、現在次のパラメーターが実装されています:
$ filter、$ inlinecount、$ orderby、$ skip、$ top ; 十分ではありませんが、サーバー側のページネーション(サーバー側のページング)などの基本的な目的には十分です。 残りのコードは、Entity Data Model(EDM)を作成します。
EntitySetメソッドは、エンティティのセットをEDMに追加します。
MapODataRouteメソッドはURIを設定し、エンドポイントを構成します。 これにより、
http:// localhost:52864 / odata / Categoryというリンクを使用できます。 JSON結果:
{ "odata.metadata":"http://localhost:52864/odata/$metadata#Category","value":[ { "ID":1,"Name":"Categoty1" },{ "ID":2,"Name":"Category2" } ] }
リンク
http:// localhost:52864 / odata / Category(1)で特定のエントリを取得するには、パラメーターがキーであるため、
GetEntityByKeyメソッドをオーバーライドする必要があります。
タスクが複雑になります。
次に、
Categoryに関連付けられた
Productモデルを作成します。
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; } }
コンテキストに行を追加することを忘れないでください
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.
REST CRUD , , , , , , . .net MVC, .
, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

, :
•
•
•
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." } } }
.

REST CRUD , , , , , , . .net MVC, .

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