diff --git a/.gitignore b/.gitignore index 74e009e..2bdfa04 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,6 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..a88e69e --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @DevExpressExampleBot \ No newline at end of file diff --git a/Images/screenshot.png b/Images/screenshot.png index de5b3e8..afbfffd 100644 Binary files a/Images/screenshot.png and b/Images/screenshot.png differ diff --git a/Readme.md b/Readme.md index f759d91..1ead8e8 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,4 @@ -![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/274919030/24.2.1%2B) [![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T902911) [![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183) [![](https://img.shields.io/badge/💬_Leave_Feedback-feecdd?style=flat-square)](#does-this-example-address-your-development-requirementsobjectives) @@ -10,30 +9,52 @@ This example prints and exports a report in a browser without previewing it on a ![Screenshot](Images/screenshot.png) -## Print +## Run the Project + +Run the following command from the *ServerApp* folder: + +```cmd +dotnet run +``` + +Run the following commands from the *react-app* folder: + +```cmd +npm install +mpm run dev +``` + +Open your browser and navigate to the URL specified in the command output to see the result. + +## Implementation Details + +### Export Operations + +The user selects the format from the drop-down list and clicks the **Export the report** button to send the format to the server-side controller. The controller calls the export method for the selected format and sends the file back to the browser. + +### Print Operations On the **server side**, a controller performs the following actions: -- [creates a report](https://docs.devexpress.com/XtraReports/2440/get-started-with-devexpress-reporting/create-a-report-from-a-to-z); -- [exports the report to PDF](https://docs.devexpress.com/XtraReports/2574/detailed-guide-to-devexpress-reporting/store-and-distribute-reports/export-reports/export-to-pdf) with the [XtraReport.ExportToPdfAsync](https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.XtraReport.ExportToPdfAsync.overloads) method; -- sends the PDF file back to the client. +- [Creates a report](https://docs.devexpress.com/XtraReports/2440/get-started-with-devexpress-reporting/create-a-report-from-a-to-z). +- [Exports the report to PDF](https://docs.devexpress.com/XtraReports/2574/detailed-guide-to-devexpress-reporting/store-and-distribute-reports/export-reports/export-to-pdf) with the [XtraReport.ExportToPdfAsync](https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.XtraReport.ExportToPdfAsync.overloads) method. +- Sends the PDF file back to the client. + +On the **client side**, a user can do one of the following: -On the **client-side**, a user can do one of the following: * Print a report in a new tab. -Click a button to call the client-side `window.Open(url, "_blank")` method to open a new window that contains a PDF file and print the window content. -* Print a report in iFrame. -Click a button to load a PDF file in the `HTMLIFrameElement` and print its content. + Click the **Print the report in a new tab** button to call the client-side `window.Open(url, "_blank")` method to open a new window that contains a PDF file and print the window content. +* Print a report in iFrame. -## Export + Click the **Print via iFrame** button to load a PDF file in the `HTMLIFrameElement` and print its content. -The user selects the format and clicks a button to send the format to the server-side controller. The controller calls the export method for the selected format and sends the file back to the browser. ## Files to Review -- [HomeComponent.jsx](dxSampleReactReportingPrintWithoutPreview/ClientApp/src/components/HomeComponent.jsx) -- [HomeController.cs](dxSampleReactReportingPrintWithoutPreview/Controllers/HomeController.cs) +- [HomeComponent.jsx](react-app/src/components/HomeComponent.jsx) +- [HomeController.cs](ServerApp/Controllers/HomeController.cs) ## Documentation @@ -46,9 +67,9 @@ The user selects the format and clicks a button to send the format to the server - [Reporting for Web (React) - Document Viewer](https://github.com/DevExpress-Examples/reporting-document-viewer-in-javascript-with-react) - [How to Print and Export a Report in the ASP.NET Core Application without the Document Viewer](https://github.com/DevExpress-Examples/Reporting-AspNetCore-Print-Without-Preview) -## Does this example address your development requirements/objectives? - -[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=reporting-react-print-without-preview&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=reporting-react-print-without-preview&~~~was_helpful=no) - +## Does this example address your development requirements/objectives? + +[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=reporting-react-print-without-preview&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=reporting-react-print-without-preview&~~~was_helpful=no) + (you will be redirected to DevExpress.com to submit your response) diff --git a/Reporting-React-Print-Without-Preview.sln b/Reporting-React-Print-Without-Preview.sln deleted file mode 100644 index 798d51f..0000000 --- a/Reporting-React-Print-Without-Preview.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29519.87 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dxSampleReactReportingPrintWithoutPreview", "dxSampleReactReportingPrintWithoutPreview\dxSampleReactReportingPrintWithoutPreview.csproj", "{C2EE00BA-5B9C-4024-A881-D32ED4EF5E46}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C2EE00BA-5B9C-4024-A881-D32ED4EF5E46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2EE00BA-5B9C-4024-A881-D32ED4EF5E46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C2EE00BA-5B9C-4024-A881-D32ED4EF5E46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C2EE00BA-5B9C-4024-A881-D32ED4EF5E46}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {C6EDD4E7-97C0-42C5-9FE3-4ECF6526A975} - EndGlobalSection -EndGlobal diff --git a/dxSampleReactReportingPrintWithoutPreview/Controllers/HomeController.cs b/ServerApp/Controllers/HomeController.cs similarity index 92% rename from dxSampleReactReportingPrintWithoutPreview/Controllers/HomeController.cs rename to ServerApp/Controllers/HomeController.cs index 69018cd..69d05b5 100644 --- a/dxSampleReactReportingPrintWithoutPreview/Controllers/HomeController.cs +++ b/ServerApp/Controllers/HomeController.cs @@ -1,92 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using DevExpress.XtraPrinting; -using DevExpress.XtraReports.UI; -using dxSampleReactReportingPrintWithoutPreview.Model; -using dxSampleReactReportingPrintWithoutPreview.PredefinedReports; -using Microsoft.AspNetCore.Mvc; - -namespace dxSampleReactReportingPrintWithoutPreview.Controllers -{ - [Route("api/[controller]")] - public class HomeController : Controller - { - public IActionResult Index() - { - return View(); - } - - public IActionResult Error() - { - ViewData["RequestId"] = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - return View(); - } - [HttpGet("[action]")] - public async Task Print() - { - var report = new TestReport(); - using (var ms = new MemoryStream()) - { - await report.ExportToPdfAsync(ms, new DevExpress.XtraPrinting.PdfExportOptions { ShowPrintDialogOnOpen = true }); - return File(ms.ToArray(), System.Net.Mime.MediaTypeNames.Application.Pdf); - } - } - [HttpGet("[action]")] - public ActionResult Export(string format = "pdf") - { - format = format.ToLower(); - XtraReport report = new TestReport(); - string contentType = string.Format("application/{0}", format); - using (MemoryStream ms = new MemoryStream()) - { - switch (format) - { - case "pdf": - contentType = "application/pdf"; - report.ExportToPdf(ms); - break; - case "docx": - contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - report.ExportToDocx(ms); - break; - case "xls": - contentType = "application/vnd.ms-excel"; - report.ExportToXls(ms); - break; - case "xlsx": - contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - report.ExportToXlsx(ms); - break; - case "rtf": - report.ExportToRtf(ms); - break; - case "mht": - contentType = "message/rfc822"; - report.ExportToMht(ms); - break; - case "html": - contentType = "text/html"; - report.ExportToHtml(ms); - break; - case "txt": - contentType = "text/plain"; - report.ExportToText(ms); - break; - case "csv": - contentType = "text/plain"; - report.ExportToCsv(ms); - break; - case "png": - contentType = "image/png"; - report.ExportToImage(ms, new ImageExportOptions() { Format = System.Drawing.Imaging.ImageFormat.Png }); - break; - } - return File(ms.ToArray(), contentType); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using DevExpress.XtraPrinting; +using DevExpress.XtraReports.UI; +using ServerApp.Model; +using ServerApp.PredefinedReports; +using Microsoft.AspNetCore.Mvc; + +namespace ServerApp.Controllers +{ + [Route("api/[controller]")] + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + + public IActionResult Error() + { + ViewData["RequestId"] = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + return View(); + } + [HttpGet("[action]")] + public async Task Print() + { + var report = new TestReport(); + using (var ms = new MemoryStream()) + { + await report.ExportToPdfAsync(ms, new DevExpress.XtraPrinting.PdfExportOptions { ShowPrintDialogOnOpen = true }); + return File(ms.ToArray(), System.Net.Mime.MediaTypeNames.Application.Pdf); + } + } + [HttpGet("[action]")] + public ActionResult Export(string format = "pdf") + { + format = format.ToLower(); + XtraReport report = new TestReport(); + string contentType = string.Format("application/{0}", format); + using (MemoryStream ms = new MemoryStream()) + { + switch (format) + { + case "pdf": + contentType = "application/pdf"; + report.ExportToPdf(ms); + break; + case "docx": + contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + report.ExportToDocx(ms); + break; + case "xls": + contentType = "application/vnd.ms-excel"; + report.ExportToXls(ms); + break; + case "xlsx": + contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + report.ExportToXlsx(ms); + break; + case "rtf": + report.ExportToRtf(ms); + break; + case "mht": + contentType = "message/rfc822"; + report.ExportToMht(ms); + break; + case "html": + contentType = "text/html"; + report.ExportToHtml(ms); + break; + case "txt": + contentType = "text/plain"; + report.ExportToText(ms); + break; + case "csv": + contentType = "text/plain"; + report.ExportToCsv(ms); + break; + case "png": + contentType = "image/png"; + report.ExportToImage(ms, new ImageExportOptions() { Format = System.Drawing.Imaging.ImageFormat.Png }); + break; + } + return File(ms.ToArray(), contentType); + } + } + } +} diff --git a/ServerApp/Controllers/ReportingControllers.cs b/ServerApp/Controllers/ReportingControllers.cs new file mode 100644 index 0000000..820bf3b --- /dev/null +++ b/ServerApp/Controllers/ReportingControllers.cs @@ -0,0 +1,45 @@ +using DevExpress.DataAccess.Sql; +using System.Collections.Generic; +using DevExpress.AspNetCore.Reporting.QueryBuilder; +using DevExpress.AspNetCore.Reporting.ReportDesigner; +using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services; +using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services; +using DevExpress.XtraReports.Web.ReportDesigner; +using DevExpress.XtraReports.Web.ReportDesigner.Services; +using DevExpress.AspNetCore.Reporting.WebDocumentViewer; +using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; +using Microsoft.AspNetCore.Mvc; + +namespace ServerApp.Controllers { + public class CustomWebDocumentViewerController : WebDocumentViewerController { + public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService) { + } + } + public class CustomReportDesignerController : ReportDesignerController { + public CustomReportDesignerController(IReportDesignerMvcControllerService controllerService) : base(controllerService) { + } + + [HttpPost("[action]")] + public IActionResult GetDesignerModel([FromForm]string reportUrl, [FromServices] IReportDesignerModelBuilder designerModelBuilder, [FromForm] ReportDesignerSettingsBase designerModelSettings) { + var ds = new SqlDataSource("NWindConnectionString"); + + // Create a SQL query to access the Products data table. + SelectQuery query = SelectQueryFluentBuilder.AddTable("Products").SelectAllColumnsFromTable().Build("Products"); + ds.Queries.Add(query); + ds.RebuildResultSchema(); + + var designerModel = designerModelBuilder.Report(reportUrl) + .DataSources(dataSources => { + dataSources.Add("Northwind", ds); + }) + .BuildModel(); + designerModel.Assign(designerModelSettings); + return DesignerModel(designerModel); + } + } + + public class CustomQueryBuilderController : QueryBuilderController { + public CustomQueryBuilderController(IQueryBuilderMvcControllerService controllerService) : base(controllerService) { + } + } +} diff --git a/ServerApp/Data/ReportDbContext.cs b/ServerApp/Data/ReportDbContext.cs new file mode 100644 index 0000000..21ce175 --- /dev/null +++ b/ServerApp/Data/ReportDbContext.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace ServerApp.Data { + public class SqlDataConnectionDescription : DataConnection { } + public class JsonDataConnectionDescription : DataConnection { } + public abstract class DataConnection { + public int Id { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public string ConnectionString { get; set; } + } + + public class ReportItem { + public int Id { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public byte[] LayoutData { get; set; } + } + + public class ReportDbContext : DbContext { + public DbSet JsonDataConnections { get; set; } + public DbSet SqlDataConnections { get; set; } + public DbSet Reports { get; set; } + public ReportDbContext(DbContextOptions options) : base(options) { + } + public void InitializeDatabase() { + Database.EnsureCreated(); + + var nwindJsonDataConnectionName = "NWindProductsJson"; + if(!JsonDataConnections.Any(x => x.Name == nwindJsonDataConnectionName)) { + var newData = new JsonDataConnectionDescription { + Name = nwindJsonDataConnectionName, + DisplayName = "Northwind Products (JSON)", + ConnectionString = "Uri=Data/nwind.json" + }; + JsonDataConnections.Add(newData); + } + + + var nwindSqlDataConnectionName = "NWindConnectionString"; + if(!SqlDataConnections.Any(x => x.Name == nwindSqlDataConnectionName)) { + var newData = new SqlDataConnectionDescription { + Name = nwindSqlDataConnectionName, + DisplayName = "Northwind Data Connection", + ConnectionString = "XpoProvider=SQLite;Data Source=|DataDirectory|Data/nwind.db" + }; + SqlDataConnections.Add(newData); + } + + var reportsDataConnectionName = "ReportsDataSqlite"; + if(!SqlDataConnections.Any(x => x.Name == reportsDataConnectionName)) { + var newData = new SqlDataConnectionDescription { + Name = reportsDataConnectionName, + DisplayName = "Reports Data (Demo)", + ConnectionString = "XpoProvider=SQLite;Data Source=|DataDirectory|Data/reportsData.db" + }; + SqlDataConnections.Add(newData); + } + SaveChanges(); + } + } +} \ No newline at end of file diff --git a/dxSampleReactReportingPrintWithoutPreview/Data/nwind.db b/ServerApp/Data/nwind.db similarity index 100% rename from dxSampleReactReportingPrintWithoutPreview/Data/nwind.db rename to ServerApp/Data/nwind.db diff --git a/dxSampleReactReportingPrintWithoutPreview/Data/nwind.json b/ServerApp/Data/nwind.json similarity index 96% rename from dxSampleReactReportingPrintWithoutPreview/Data/nwind.json rename to ServerApp/Data/nwind.json index 1c6fea5..49aa018 100644 --- a/dxSampleReactReportingPrintWithoutPreview/Data/nwind.json +++ b/ServerApp/Data/nwind.json @@ -1,1005 +1,1005 @@ -{ - "Products": [ - { - "ProductID": 1, - "ProductName": "Chai", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "10 boxes x 20 bags", - "UnitPrice": 18, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900001" - }, - { - "ProductID": 2, - "ProductName": "Chang", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 19, - "UnitsInStock": 17, - "UnitsOnOrder": 40, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900002" - }, - { - "ProductID": 3, - "ProductName": "Aniseed Syrup", - "SupplierID": 1, - "CategoryID": 2, - "QuantityPerUnit": "12 - 550 ml bottles", - "UnitPrice": 10, - "UnitsInStock": 13, - "UnitsOnOrder": 70, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900003" - }, - { - "ProductID": 4, - "ProductName": "Chef Anton's Cajun Seasoning", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "48 - 6 oz jars", - "UnitPrice": 22, - "UnitsInStock": 53, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900004" - }, - { - "ProductID": 5, - "ProductName": "Chef Anton's Gumbo Mix", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "36 boxes", - "UnitPrice": 21.35, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900005" - }, - { - "ProductID": 6, - "ProductName": "Grandma's Boysenberry Spread", - "SupplierID": 3, - "CategoryID": 2, - "QuantityPerUnit": "12 - 8 oz jars", - "UnitPrice": 25, - "UnitsInStock": 120, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900006" - }, - { - "ProductID": 7, - "ProductName": "Uncle Bob's Organic Dried Pears", - "SupplierID": 3, - "CategoryID": 7, - "QuantityPerUnit": "12 - 1 lb pkgs.", - "UnitPrice": 30, - "UnitsInStock": 15, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900007" - }, - { - "ProductID": 8, - "ProductName": "Northwoods Cranberry Sauce", - "SupplierID": 3, - "CategoryID": 2, - "QuantityPerUnit": "12 - 12 oz jars", - "UnitPrice": 40, - "UnitsInStock": 6, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900008" - }, - { - "ProductID": 9, - "ProductName": "Mishi Kobe Niku", - "SupplierID": 4, - "CategoryID": 6, - "QuantityPerUnit": "18 - 500 g pkgs.", - "UnitPrice": 97, - "UnitsInStock": 29, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900009" - }, - { - "ProductID": 10, - "ProductName": "Ikura", - "SupplierID": 4, - "CategoryID": 8, - "QuantityPerUnit": "12 - 200 ml jars", - "UnitPrice": 31, - "UnitsInStock": 31, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900010" - }, - { - "ProductID": 11, - "ProductName": "Queso Cabrales", - "SupplierID": 5, - "CategoryID": 4, - "QuantityPerUnit": "1 kg pkg.", - "UnitPrice": 21, - "UnitsInStock": 22, - "UnitsOnOrder": 30, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900011" - }, - { - "ProductID": 12, - "ProductName": "Queso Manchego La Pastora", - "SupplierID": 5, - "CategoryID": 4, - "QuantityPerUnit": "10 - 500 g pkgs.", - "UnitPrice": 38, - "UnitsInStock": 86, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900012" - }, - { - "ProductID": 13, - "ProductName": "Konbu", - "SupplierID": 6, - "CategoryID": 8, - "QuantityPerUnit": "2 kg box", - "UnitPrice": 6, - "UnitsInStock": 24, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900013" - }, - { - "ProductID": 14, - "ProductName": "Tofu", - "SupplierID": 6, - "CategoryID": 7, - "QuantityPerUnit": "40 - 100 g pkgs.", - "UnitPrice": 23.25, - "UnitsInStock": 35, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900014" - }, - { - "ProductID": 15, - "ProductName": "Genen Shouyu", - "SupplierID": 6, - "CategoryID": 2, - "QuantityPerUnit": "24 - 250 ml bottles", - "UnitPrice": 15.5, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900015" - }, - { - "ProductID": 16, - "ProductName": "Pavlova", - "SupplierID": 7, - "CategoryID": 3, - "QuantityPerUnit": "32 - 500 g boxes", - "UnitPrice": 17.45, - "UnitsInStock": 29, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900016" - }, - { - "ProductID": 17, - "ProductName": "Alice Mutton", - "SupplierID": 7, - "CategoryID": 6, - "QuantityPerUnit": "20 - 1 kg tins", - "UnitPrice": 39, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900017" - }, - { - "ProductID": 18, - "ProductName": "Carnarvon Tigers", - "SupplierID": 7, - "CategoryID": 8, - "QuantityPerUnit": "16 kg pkg.", - "UnitPrice": 62.5, - "UnitsInStock": 42, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900018" - }, - { - "ProductID": 19, - "ProductName": "Teatime Chocolate Biscuits", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "10 boxes x 12 pieces", - "UnitPrice": 9.2, - "UnitsInStock": 25, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900019" - }, - { - "ProductID": 20, - "ProductName": "Sir Rodney's Marmalade", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "30 gift boxes", - "UnitPrice": 81, - "UnitsInStock": 40, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900020" - }, - { - "ProductID": 21, - "ProductName": "Sir Rodney's Scones", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "24 pkgs. x 4 pieces", - "UnitPrice": 10, - "UnitsInStock": 3, - "UnitsOnOrder": 40, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900021" - }, - { - "ProductID": 22, - "ProductName": "Gustaf's Knäckebröd", - "SupplierID": 9, - "CategoryID": 5, - "QuantityPerUnit": "24 - 500 g pkgs.", - "UnitPrice": 21, - "UnitsInStock": 104, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900022" - }, - { - "ProductID": 23, - "ProductName": "Tunnbröd", - "SupplierID": 9, - "CategoryID": 5, - "QuantityPerUnit": "12 - 250 g pkgs.", - "UnitPrice": 9, - "UnitsInStock": 61, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900023" - }, - { - "ProductID": 24, - "ProductName": "Guaraná Fantástica", - "SupplierID": 10, - "CategoryID": 1, - "QuantityPerUnit": "12 - 355 ml cans", - "UnitPrice": 4.5, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900024" - }, - { - "ProductID": 25, - "ProductName": "NuNuCa Nuß-Nougat-Creme", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "20 - 450 g glasses", - "UnitPrice": 14, - "UnitsInStock": 76, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900025" - }, - { - "ProductID": 26, - "ProductName": "Gumbär Gummibärchen", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "100 - 250 g bags", - "UnitPrice": 31.23, - "UnitsInStock": 15, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900026" - }, - { - "ProductID": 27, - "ProductName": "Schoggi Schokolade", - "SupplierID": 11, - "CategoryID": 3, - "QuantityPerUnit": "100 - 100 g pieces", - "UnitPrice": 43.9, - "UnitsInStock": 49, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900027" - }, - { - "ProductID": 28, - "ProductName": "Rössle Sauerkraut", - "SupplierID": 12, - "CategoryID": 7, - "QuantityPerUnit": "25 - 825 g cans", - "UnitPrice": 45.6, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900028" - }, - { - "ProductID": 29, - "ProductName": "Thüringer Rostbratwurst", - "SupplierID": 12, - "CategoryID": 6, - "QuantityPerUnit": "50 bags x 30 sausgs.", - "UnitPrice": 123.79, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900029" - }, - { - "ProductID": 30, - "ProductName": "Nord-Ost Matjeshering", - "SupplierID": 13, - "CategoryID": 8, - "QuantityPerUnit": "10 - 200 g glasses", - "UnitPrice": 25.89, - "UnitsInStock": 10, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900030" - }, - { - "ProductID": 31, - "ProductName": "Gorgonzola Telino", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "12 - 100 g pkgs", - "UnitPrice": 12.5, - "UnitsInStock": 0, - "UnitsOnOrder": 70, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900031" - }, - { - "ProductID": 32, - "ProductName": "Mascarpone Fabioli", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "24 - 200 g pkgs.", - "UnitPrice": 32, - "UnitsInStock": 9, - "UnitsOnOrder": 40, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900032" - }, - { - "ProductID": 33, - "ProductName": "Geitost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "500 g", - "UnitPrice": 2.5, - "UnitsInStock": 112, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900033" - }, - { - "ProductID": 34, - "ProductName": "Sasquatch Ale", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 14, - "UnitsInStock": 111, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900034" - }, - { - "ProductID": 35, - "ProductName": "Steeleye Stout", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 18, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900035" - }, - { - "ProductID": 36, - "ProductName": "Inlagd Sill", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "24 - 250 g jars", - "UnitPrice": 19, - "UnitsInStock": 112, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900036" - }, - { - "ProductID": 37, - "ProductName": "Gravad lax", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "12 - 500 g pkgs.", - "UnitPrice": 26, - "UnitsInStock": 11, - "UnitsOnOrder": 50, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900037" - }, - { - "ProductID": 38, - "ProductName": "Côte de Blaye", - "SupplierID": 18, - "CategoryID": 1, - "QuantityPerUnit": "12 - 75 cl bottles", - "UnitPrice": 263.5, - "UnitsInStock": 17, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900038" - }, - { - "ProductID": 39, - "ProductName": "Chartreuse verte", - "SupplierID": 18, - "CategoryID": 1, - "QuantityPerUnit": "750 cc per bottle", - "UnitPrice": 18, - "UnitsInStock": 69, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900039" - }, - { - "ProductID": 40, - "ProductName": "Boston Crab Meat", - "SupplierID": 19, - "CategoryID": 8, - "QuantityPerUnit": "24 - 4 oz tins", - "UnitPrice": 18.4, - "UnitsInStock": 123, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900040" - }, - { - "ProductID": 41, - "ProductName": "Jack's New England Clam Chowder", - "SupplierID": 19, - "CategoryID": 8, - "QuantityPerUnit": "12 - 12 oz cans", - "UnitPrice": 9.65, - "UnitsInStock": 85, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900041" - }, - { - "ProductID": 42, - "ProductName": "Singaporean Hokkien Fried Mee", - "SupplierID": 20, - "CategoryID": 5, - "QuantityPerUnit": "32 - 1 kg pkgs.", - "UnitPrice": 14, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900042" - }, - { - "ProductID": 43, - "ProductName": "Ipoh Coffee", - "SupplierID": 20, - "CategoryID": 1, - "QuantityPerUnit": "16 - 500 g tins", - "UnitPrice": 46, - "UnitsInStock": 17, - "UnitsOnOrder": 10, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900043" - }, - { - "ProductID": 44, - "ProductName": "Gula Malacca", - "SupplierID": 20, - "CategoryID": 2, - "QuantityPerUnit": "20 - 2 kg bags", - "UnitPrice": 19.45, - "UnitsInStock": 27, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900044" - }, - { - "ProductID": 45, - "ProductName": "Rogede sild", - "SupplierID": 21, - "CategoryID": 8, - "QuantityPerUnit": "1k pkg.", - "UnitPrice": 9.5, - "UnitsInStock": 5, - "UnitsOnOrder": 70, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900045" - }, - { - "ProductID": 46, - "ProductName": "Spegesild", - "SupplierID": 21, - "CategoryID": 8, - "QuantityPerUnit": "4 - 450 g glasses", - "UnitPrice": 12, - "UnitsInStock": 95, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900046" - }, - { - "ProductID": 47, - "ProductName": "Zaanse koeken", - "SupplierID": 22, - "CategoryID": 3, - "QuantityPerUnit": "10 - 4 oz boxes", - "UnitPrice": 9.5, - "UnitsInStock": 36, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900047" - }, - { - "ProductID": 48, - "ProductName": "Chocolade", - "SupplierID": 22, - "CategoryID": 3, - "QuantityPerUnit": "10 pkgs.", - "UnitPrice": 12.75, - "UnitsInStock": 15, - "UnitsOnOrder": 70, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900048" - }, - { - "ProductID": 49, - "ProductName": "Maxilaku", - "SupplierID": 23, - "CategoryID": 3, - "QuantityPerUnit": "24 - 50 g pkgs.", - "UnitPrice": 20, - "UnitsInStock": 10, - "UnitsOnOrder": 60, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900049" - }, - { - "ProductID": 50, - "ProductName": "Valkoinen suklaa", - "SupplierID": 23, - "CategoryID": 3, - "QuantityPerUnit": "12 - 100 g bars", - "UnitPrice": 16.25, - "UnitsInStock": 65, - "UnitsOnOrder": 0, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900050" - }, - { - "ProductID": 51, - "ProductName": "Manjimup Dried Apples", - "SupplierID": 24, - "CategoryID": 7, - "QuantityPerUnit": "50 - 300 g pkgs.", - "UnitPrice": 53, - "UnitsInStock": 20, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900051" - }, - { - "ProductID": 52, - "ProductName": "Filo Mix", - "SupplierID": 24, - "CategoryID": 5, - "QuantityPerUnit": "16 - 2 kg boxes", - "UnitPrice": 7, - "UnitsInStock": 38, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900052" - }, - { - "ProductID": 53, - "ProductName": "Perth Pasties", - "SupplierID": 24, - "CategoryID": 6, - "QuantityPerUnit": "48 pieces", - "UnitPrice": 32.8, - "UnitsInStock": 0, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 1, - "EAN13": "070684900053" - }, - { - "ProductID": 54, - "ProductName": "Tourtière", - "SupplierID": 25, - "CategoryID": 6, - "QuantityPerUnit": "16 pies", - "UnitPrice": 7.45, - "UnitsInStock": 21, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900054" - }, - { - "ProductID": 55, - "ProductName": "Pâté chinois", - "SupplierID": 25, - "CategoryID": 6, - "QuantityPerUnit": "24 boxes x 2 pies", - "UnitPrice": 24, - "UnitsInStock": 115, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900055" - }, - { - "ProductID": 56, - "ProductName": "Gnocchi di nonna Alice", - "SupplierID": 26, - "CategoryID": 5, - "QuantityPerUnit": "24 - 250 g pkgs.", - "UnitPrice": 38, - "UnitsInStock": 21, - "UnitsOnOrder": 10, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900056" - }, - { - "ProductID": 57, - "ProductName": "Ravioli Angelo", - "SupplierID": 26, - "CategoryID": 5, - "QuantityPerUnit": "24 - 250 g pkgs.", - "UnitPrice": 19.5, - "UnitsInStock": 36, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900057" - }, - { - "ProductID": 58, - "ProductName": "Escargots de Bourgogne", - "SupplierID": 27, - "CategoryID": 8, - "QuantityPerUnit": "24 pieces", - "UnitPrice": 13.25, - "UnitsInStock": 62, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900058" - }, - { - "ProductID": 59, - "ProductName": "Raclette Courdavault", - "SupplierID": 28, - "CategoryID": 4, - "QuantityPerUnit": "5 kg pkg.", - "UnitPrice": 55, - "UnitsInStock": 79, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900059" - }, - { - "ProductID": 60, - "ProductName": "Camembert Pierrot", - "SupplierID": 28, - "CategoryID": 4, - "QuantityPerUnit": "15 - 300 g rounds", - "UnitPrice": 34, - "UnitsInStock": 19, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900060" - }, - { - "ProductID": 61, - "ProductName": "Sirop d'érable", - "SupplierID": 29, - "CategoryID": 2, - "QuantityPerUnit": "24 - 500 ml bottles", - "UnitPrice": 28.5, - "UnitsInStock": 113, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900061" - }, - { - "ProductID": 62, - "ProductName": "Tarte au sucre", - "SupplierID": 29, - "CategoryID": 3, - "QuantityPerUnit": "48 pies", - "UnitPrice": 49.3, - "UnitsInStock": 17, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900062" - }, - { - "ProductID": 63, - "ProductName": "Vegie-spread", - "SupplierID": 7, - "CategoryID": 2, - "QuantityPerUnit": "15 - 625 g jars", - "UnitPrice": 43.9, - "UnitsInStock": 24, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900063" - }, - { - "ProductID": 64, - "ProductName": "Wimmers gute Semmelknödel", - "SupplierID": 12, - "CategoryID": 5, - "QuantityPerUnit": "20 bags x 4 pieces", - "UnitPrice": 33.25, - "UnitsInStock": 22, - "UnitsOnOrder": 80, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900064" - }, - { - "ProductID": 65, - "ProductName": "Louisiana Fiery Hot Pepper Sauce", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "32 - 8 oz bottles", - "UnitPrice": 21.05, - "UnitsInStock": 76, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900065" - }, - { - "ProductID": 66, - "ProductName": "Louisiana Hot Spiced Okra", - "SupplierID": 2, - "CategoryID": 2, - "QuantityPerUnit": "24 - 8 oz jars", - "UnitPrice": 17, - "UnitsInStock": 4, - "UnitsOnOrder": 100, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900066" - }, - { - "ProductID": 67, - "ProductName": "Laughing Lumberjack Lager", - "SupplierID": 16, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 14, - "UnitsInStock": 52, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": 0, - "EAN13": "070684900067" - }, - { - "ProductID": 68, - "ProductName": "Scottish Longbreads", - "SupplierID": 8, - "CategoryID": 3, - "QuantityPerUnit": "10 boxes x 8 pieces", - "UnitPrice": 12.5, - "UnitsInStock": 6, - "UnitsOnOrder": 10, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900068" - }, - { - "ProductID": 69, - "ProductName": "Gudbrandsdalsost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "10 kg pkg.", - "UnitPrice": 36, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900069" - }, - { - "ProductID": 70, - "ProductName": "Outback Lager", - "SupplierID": 7, - "CategoryID": 1, - "QuantityPerUnit": "24 - 355 ml bottles", - "UnitPrice": 15, - "UnitsInStock": 15, - "UnitsOnOrder": 10, - "ReorderLevel": 30, - "Discontinued": 0, - "EAN13": "070684900070" - }, - { - "ProductID": 71, - "ProductName": "Flotemysost", - "SupplierID": 15, - "CategoryID": 4, - "QuantityPerUnit": "10 - 500 g pkgs.", - "UnitPrice": 21.5, - "UnitsInStock": 26, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900071" - }, - { - "ProductID": 72, - "ProductName": "Mozzarella di Giovanni", - "SupplierID": 14, - "CategoryID": 4, - "QuantityPerUnit": "24 - 200 g pkgs.", - "UnitPrice": 34.8, - "UnitsInStock": 14, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": 0, - "EAN13": "070684900072" - }, - { - "ProductID": 73, - "ProductName": "Röd Kaviar", - "SupplierID": 17, - "CategoryID": 8, - "QuantityPerUnit": "24 - 150 g jars", - "UnitPrice": 15, - "UnitsInStock": 101, - "UnitsOnOrder": 0, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900073" - }, - { - "ProductID": 74, - "ProductName": "Longlife Tofu", - "SupplierID": 4, - "CategoryID": 7, - "QuantityPerUnit": "5 kg pkg.", - "UnitPrice": 10, - "UnitsInStock": 4, - "UnitsOnOrder": 20, - "ReorderLevel": 5, - "Discontinued": 0, - "EAN13": "070684900074" - }, - { - "ProductID": 75, - "ProductName": "Rhönbräu Klosterbier", - "SupplierID": 12, - "CategoryID": 1, - "QuantityPerUnit": "24 - 0.5 l bottles", - "UnitPrice": 7.75, - "UnitsInStock": 125, - "UnitsOnOrder": 0, - "ReorderLevel": 25, - "Discontinued": 0, - "EAN13": "070684900075" - }, - { - "ProductID": 76, - "ProductName": "Lakkalikööri", - "SupplierID": 23, - "CategoryID": 1, - "QuantityPerUnit": "500 ml", - "UnitPrice": 18, - "UnitsInStock": 57, - "UnitsOnOrder": 0, - "ReorderLevel": 20, - "Discontinued": 0, - "EAN13": "070684900076" - }, - { - "ProductID": 77, - "ProductName": "Original Frankfurter grüne Soße", - "SupplierID": 12, - "CategoryID": 2, - "QuantityPerUnit": "12 boxes", - "UnitPrice": 13, - "UnitsInStock": 32, - "UnitsOnOrder": 0, - "ReorderLevel": 15, - "Discontinued": 0, - "EAN13": "070684900077" - } - ] -} +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900001" + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900002" + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900003" + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900004" + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900005" + }, + { + "ProductID": 6, + "ProductName": "Grandma's Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 120, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900006" + }, + { + "ProductID": 7, + "ProductName": "Uncle Bob's Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900007" + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900008" + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900009" + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900010" + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900011" + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900012" + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900013" + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900014" + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900015" + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900016" + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900017" + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900018" + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 12 pieces", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900019" + }, + { + "ProductID": 20, + "ProductName": "Sir Rodney's Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "30 gift boxes", + "UnitPrice": 81, + "UnitsInStock": 40, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900020" + }, + { + "ProductID": 21, + "ProductName": "Sir Rodney's Scones", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "24 pkgs. x 4 pieces", + "UnitPrice": 10, + "UnitsInStock": 3, + "UnitsOnOrder": 40, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900021" + }, + { + "ProductID": 22, + "ProductName": "Gustaf's Knäckebröd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "24 - 500 g pkgs.", + "UnitPrice": 21, + "UnitsInStock": 104, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900022" + }, + { + "ProductID": 23, + "ProductName": "Tunnbröd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "12 - 250 g pkgs.", + "UnitPrice": 9, + "UnitsInStock": 61, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900023" + }, + { + "ProductID": 24, + "ProductName": "Guaraná Fantástica", + "SupplierID": 10, + "CategoryID": 1, + "QuantityPerUnit": "12 - 355 ml cans", + "UnitPrice": 4.5, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900024" + }, + { + "ProductID": 25, + "ProductName": "NuNuCa Nuß-Nougat-Creme", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "20 - 450 g glasses", + "UnitPrice": 14, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900025" + }, + { + "ProductID": 26, + "ProductName": "Gumbär Gummibärchen", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 250 g bags", + "UnitPrice": 31.23, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900026" + }, + { + "ProductID": 27, + "ProductName": "Schoggi Schokolade", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 100 g pieces", + "UnitPrice": 43.9, + "UnitsInStock": 49, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900027" + }, + { + "ProductID": 28, + "ProductName": "Rössle Sauerkraut", + "SupplierID": 12, + "CategoryID": 7, + "QuantityPerUnit": "25 - 825 g cans", + "UnitPrice": 45.6, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900028" + }, + { + "ProductID": 29, + "ProductName": "Thüringer Rostbratwurst", + "SupplierID": 12, + "CategoryID": 6, + "QuantityPerUnit": "50 bags x 30 sausgs.", + "UnitPrice": 123.79, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900029" + }, + { + "ProductID": 30, + "ProductName": "Nord-Ost Matjeshering", + "SupplierID": 13, + "CategoryID": 8, + "QuantityPerUnit": "10 - 200 g glasses", + "UnitPrice": 25.89, + "UnitsInStock": 10, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900030" + }, + { + "ProductID": 31, + "ProductName": "Gorgonzola Telino", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "12 - 100 g pkgs", + "UnitPrice": 12.5, + "UnitsInStock": 0, + "UnitsOnOrder": 70, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900031" + }, + { + "ProductID": 32, + "ProductName": "Mascarpone Fabioli", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 32, + "UnitsInStock": 9, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900032" + }, + { + "ProductID": 33, + "ProductName": "Geitost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "500 g", + "UnitPrice": 2.5, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900033" + }, + { + "ProductID": 34, + "ProductName": "Sasquatch Ale", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 111, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900034" + }, + { + "ProductID": 35, + "ProductName": "Steeleye Stout", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 18, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900035" + }, + { + "ProductID": 36, + "ProductName": "Inlagd Sill", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 250 g jars", + "UnitPrice": 19, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900036" + }, + { + "ProductID": 37, + "ProductName": "Gravad lax", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "12 - 500 g pkgs.", + "UnitPrice": 26, + "UnitsInStock": 11, + "UnitsOnOrder": 50, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900037" + }, + { + "ProductID": 38, + "ProductName": "Côte de Blaye", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "12 - 75 cl bottles", + "UnitPrice": 263.5, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900038" + }, + { + "ProductID": 39, + "ProductName": "Chartreuse verte", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "750 cc per bottle", + "UnitPrice": 18, + "UnitsInStock": 69, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900039" + }, + { + "ProductID": 40, + "ProductName": "Boston Crab Meat", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "24 - 4 oz tins", + "UnitPrice": 18.4, + "UnitsInStock": 123, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900040" + }, + { + "ProductID": 41, + "ProductName": "Jack's New England Clam Chowder", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "12 - 12 oz cans", + "UnitPrice": 9.65, + "UnitsInStock": 85, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900041" + }, + { + "ProductID": 42, + "ProductName": "Singaporean Hokkien Fried Mee", + "SupplierID": 20, + "CategoryID": 5, + "QuantityPerUnit": "32 - 1 kg pkgs.", + "UnitPrice": 14, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900042" + }, + { + "ProductID": 43, + "ProductName": "Ipoh Coffee", + "SupplierID": 20, + "CategoryID": 1, + "QuantityPerUnit": "16 - 500 g tins", + "UnitPrice": 46, + "UnitsInStock": 17, + "UnitsOnOrder": 10, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900043" + }, + { + "ProductID": 44, + "ProductName": "Gula Malacca", + "SupplierID": 20, + "CategoryID": 2, + "QuantityPerUnit": "20 - 2 kg bags", + "UnitPrice": 19.45, + "UnitsInStock": 27, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900044" + }, + { + "ProductID": 45, + "ProductName": "Rogede sild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "1k pkg.", + "UnitPrice": 9.5, + "UnitsInStock": 5, + "UnitsOnOrder": 70, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900045" + }, + { + "ProductID": 46, + "ProductName": "Spegesild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "4 - 450 g glasses", + "UnitPrice": 12, + "UnitsInStock": 95, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900046" + }, + { + "ProductID": 47, + "ProductName": "Zaanse koeken", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 - 4 oz boxes", + "UnitPrice": 9.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900047" + }, + { + "ProductID": 48, + "ProductName": "Chocolade", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 pkgs.", + "UnitPrice": 12.75, + "UnitsInStock": 15, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900048" + }, + { + "ProductID": 49, + "ProductName": "Maxilaku", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "24 - 50 g pkgs.", + "UnitPrice": 20, + "UnitsInStock": 10, + "UnitsOnOrder": 60, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900049" + }, + { + "ProductID": 50, + "ProductName": "Valkoinen suklaa", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "12 - 100 g bars", + "UnitPrice": 16.25, + "UnitsInStock": 65, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900050" + }, + { + "ProductID": 51, + "ProductName": "Manjimup Dried Apples", + "SupplierID": 24, + "CategoryID": 7, + "QuantityPerUnit": "50 - 300 g pkgs.", + "UnitPrice": 53, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900051" + }, + { + "ProductID": 52, + "ProductName": "Filo Mix", + "SupplierID": 24, + "CategoryID": 5, + "QuantityPerUnit": "16 - 2 kg boxes", + "UnitPrice": 7, + "UnitsInStock": 38, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900052" + }, + { + "ProductID": 53, + "ProductName": "Perth Pasties", + "SupplierID": 24, + "CategoryID": 6, + "QuantityPerUnit": "48 pieces", + "UnitPrice": 32.8, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900053" + }, + { + "ProductID": 54, + "ProductName": "Tourtière", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "16 pies", + "UnitPrice": 7.45, + "UnitsInStock": 21, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900054" + }, + { + "ProductID": 55, + "ProductName": "Pâté chinois", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "24 boxes x 2 pies", + "UnitPrice": 24, + "UnitsInStock": 115, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900055" + }, + { + "ProductID": 56, + "ProductName": "Gnocchi di nonna Alice", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 21, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900056" + }, + { + "ProductID": 57, + "ProductName": "Ravioli Angelo", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 19.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900057" + }, + { + "ProductID": 58, + "ProductName": "Escargots de Bourgogne", + "SupplierID": 27, + "CategoryID": 8, + "QuantityPerUnit": "24 pieces", + "UnitPrice": 13.25, + "UnitsInStock": 62, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900058" + }, + { + "ProductID": 59, + "ProductName": "Raclette Courdavault", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 55, + "UnitsInStock": 79, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900059" + }, + { + "ProductID": 60, + "ProductName": "Camembert Pierrot", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "15 - 300 g rounds", + "UnitPrice": 34, + "UnitsInStock": 19, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900060" + }, + { + "ProductID": 61, + "ProductName": "Sirop d'érable", + "SupplierID": 29, + "CategoryID": 2, + "QuantityPerUnit": "24 - 500 ml bottles", + "UnitPrice": 28.5, + "UnitsInStock": 113, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900061" + }, + { + "ProductID": 62, + "ProductName": "Tarte au sucre", + "SupplierID": 29, + "CategoryID": 3, + "QuantityPerUnit": "48 pies", + "UnitPrice": 49.3, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900062" + }, + { + "ProductID": 63, + "ProductName": "Vegie-spread", + "SupplierID": 7, + "CategoryID": 2, + "QuantityPerUnit": "15 - 625 g jars", + "UnitPrice": 43.9, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900063" + }, + { + "ProductID": 64, + "ProductName": "Wimmers gute Semmelknödel", + "SupplierID": 12, + "CategoryID": 5, + "QuantityPerUnit": "20 bags x 4 pieces", + "UnitPrice": 33.25, + "UnitsInStock": 22, + "UnitsOnOrder": 80, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900064" + }, + { + "ProductID": 65, + "ProductName": "Louisiana Fiery Hot Pepper Sauce", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "32 - 8 oz bottles", + "UnitPrice": 21.05, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900065" + }, + { + "ProductID": 66, + "ProductName": "Louisiana Hot Spiced Okra", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "24 - 8 oz jars", + "UnitPrice": 17, + "UnitsInStock": 4, + "UnitsOnOrder": 100, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900066" + }, + { + "ProductID": 67, + "ProductName": "Laughing Lumberjack Lager", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 52, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900067" + }, + { + "ProductID": 68, + "ProductName": "Scottish Longbreads", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 8 pieces", + "UnitPrice": 12.5, + "UnitsInStock": 6, + "UnitsOnOrder": 10, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900068" + }, + { + "ProductID": 69, + "ProductName": "Gudbrandsdalsost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 kg pkg.", + "UnitPrice": 36, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900069" + }, + { + "ProductID": 70, + "ProductName": "Outback Lager", + "SupplierID": 7, + "CategoryID": 1, + "QuantityPerUnit": "24 - 355 ml bottles", + "UnitPrice": 15, + "UnitsInStock": 15, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900070" + }, + { + "ProductID": 71, + "ProductName": "Flotemysost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 21.5, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900071" + }, + { + "ProductID": 72, + "ProductName": "Mozzarella di Giovanni", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 34.8, + "UnitsInStock": 14, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900072" + }, + { + "ProductID": 73, + "ProductName": "Röd Kaviar", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 150 g jars", + "UnitPrice": 15, + "UnitsInStock": 101, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900073" + }, + { + "ProductID": 74, + "ProductName": "Longlife Tofu", + "SupplierID": 4, + "CategoryID": 7, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 10, + "UnitsInStock": 4, + "UnitsOnOrder": 20, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900074" + }, + { + "ProductID": 75, + "ProductName": "Rhönbräu Klosterbier", + "SupplierID": 12, + "CategoryID": 1, + "QuantityPerUnit": "24 - 0.5 l bottles", + "UnitPrice": 7.75, + "UnitsInStock": 125, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900075" + }, + { + "ProductID": 76, + "ProductName": "Lakkalikööri", + "SupplierID": 23, + "CategoryID": 1, + "QuantityPerUnit": "500 ml", + "UnitPrice": 18, + "UnitsInStock": 57, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900076" + }, + { + "ProductID": 77, + "ProductName": "Original Frankfurter grüne Soße", + "SupplierID": 12, + "CategoryID": 2, + "QuantityPerUnit": "12 boxes", + "UnitPrice": 13, + "UnitsInStock": 32, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900077" + } + ] +} diff --git a/ServerApp/Data/reportsData.db b/ServerApp/Data/reportsData.db new file mode 100644 index 0000000..7a11360 Binary files /dev/null and b/ServerApp/Data/reportsData.db differ diff --git a/dxSampleReactReportingPrintWithoutPreview/Model/ExportModel.cs b/ServerApp/Model/ExportModel.cs similarity index 72% rename from dxSampleReactReportingPrintWithoutPreview/Model/ExportModel.cs rename to ServerApp/Model/ExportModel.cs index cebabae..925ea66 100644 --- a/dxSampleReactReportingPrintWithoutPreview/Model/ExportModel.cs +++ b/ServerApp/Model/ExportModel.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace dxSampleReactReportingPrintWithoutPreview.Model -{ - public class ExportModel - { - public string Format { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ServerApp.Model +{ + public class ExportModel + { + public string Format { get; set; } + } +} diff --git a/ServerApp/Pages/Error.cshtml b/ServerApp/Pages/Error.cshtml new file mode 100644 index 0000000..b1f3143 --- /dev/null +++ b/ServerApp/Pages/Error.cshtml @@ -0,0 +1,23 @@ +@page +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml.cs b/ServerApp/Pages/Error.cshtml.cs similarity index 67% rename from dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml.cs rename to ServerApp/Pages/Error.cshtml.cs index 269fff4..edc520f 100644 --- a/dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml.cs +++ b/ServerApp/Pages/Error.cshtml.cs @@ -1,31 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace dxSampleReactReportingPrintWithoutPreview.Pages -{ - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public class ErrorModel : PageModel - { - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace ServerApp.Pages +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/ServerApp/Pages/_ViewImports.cshtml b/ServerApp/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..099b708 --- /dev/null +++ b/ServerApp/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ServerApp +@namespace ServerApp.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/ReportsFactory.cs b/ServerApp/PredefinedReports/ReportsFactory.cs similarity index 81% rename from dxSampleReactReportingPrintWithoutPreview/PredefinedReports/ReportsFactory.cs rename to ServerApp/PredefinedReports/ReportsFactory.cs index dd7ec5c..d111bc5 100644 --- a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/ReportsFactory.cs +++ b/ServerApp/PredefinedReports/ReportsFactory.cs @@ -1,16 +1,16 @@ -using DevExpress.XtraReports.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace dxSampleReactReportingPrintWithoutPreview.PredefinedReports -{ - public static class ReportsFactory - { - public static Dictionary> Reports = new Dictionary>() - { - ["TestReport"] = () => new TestReport() - }; - } -} +using DevExpress.XtraReports.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ServerApp.PredefinedReports +{ + public static class ReportsFactory + { + public static Dictionary> Reports = new Dictionary>() + { + ["TestReport"] = () => new TestReport() + }; + } +} diff --git a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.Designer.cs b/ServerApp/PredefinedReports/TestReport.Designer.cs similarity index 94% rename from dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.Designer.cs rename to ServerApp/PredefinedReports/TestReport.Designer.cs index 2ad33d4..b3c7f1a 100644 --- a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.Designer.cs +++ b/ServerApp/PredefinedReports/TestReport.Designer.cs @@ -1,74 +1,74 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace dxSampleReactReportingPrintWithoutPreview.PredefinedReports { - - public partial class TestReport : DevExpress.XtraReports.UI.XtraReport { - private void InitializeComponent() { - DevExpress.XtraReports.ReportInitializer reportInitializer = new DevExpress.XtraReports.ReportInitializer(this, "dxSampleReactReportingPrintWithoutPreview.PredefinedReports.TestReport.repx"); - - // Controls - this.TopMargin = reportInitializer.GetControl("TopMargin"); - this.ReportHeader = reportInitializer.GetControl("ReportHeader"); - this.GroupHeader1 = reportInitializer.GetControl("GroupHeader1"); - this.Detail = reportInitializer.GetControl("Detail"); - this.BottomMargin = reportInitializer.GetControl("BottomMargin"); - this.label1 = reportInitializer.GetControl("label1"); - this.table1 = reportInitializer.GetControl("table1"); - this.tableRow1 = reportInitializer.GetControl("tableRow1"); - this.tableCell1 = reportInitializer.GetControl("tableCell1"); - this.tableCell2 = reportInitializer.GetControl("tableCell2"); - this.tableCell3 = reportInitializer.GetControl("tableCell3"); - this.table2 = reportInitializer.GetControl("table2"); - this.tableRow2 = reportInitializer.GetControl("tableRow2"); - this.tableCell4 = reportInitializer.GetControl("tableCell4"); - this.tableCell5 = reportInitializer.GetControl("tableCell5"); - this.tableCell6 = reportInitializer.GetControl("tableCell6"); - this.pictureBox1 = reportInitializer.GetControl("pictureBox1"); - this.pageInfo1 = reportInitializer.GetControl("pageInfo1"); - this.pageInfo2 = reportInitializer.GetControl("pageInfo2"); - - // Data Sources - this.sqlDataSource1 = reportInitializer.GetDataSource("sqlDataSource1"); - - // Styles - this.Title = reportInitializer.GetStyle("Title"); - this.DetailCaption1 = reportInitializer.GetStyle("DetailCaption1"); - this.DetailData1 = reportInitializer.GetStyle("DetailData1"); - this.DetailData3_Odd = reportInitializer.GetStyle("DetailData3_Odd"); - this.PageInfo = reportInitializer.GetStyle("PageInfo"); - } - private DevExpress.XtraReports.UI.TopMarginBand TopMargin; - private DevExpress.XtraReports.UI.ReportHeaderBand ReportHeader; - private DevExpress.XtraReports.UI.GroupHeaderBand GroupHeader1; - private DevExpress.XtraReports.UI.DetailBand Detail; - private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; - private DevExpress.XtraReports.UI.XRLabel label1; - private DevExpress.XtraReports.UI.XRTable table1; - private DevExpress.XtraReports.UI.XRTableRow tableRow1; - private DevExpress.XtraReports.UI.XRTableCell tableCell1; - private DevExpress.XtraReports.UI.XRTableCell tableCell2; - private DevExpress.XtraReports.UI.XRTableCell tableCell3; - private DevExpress.XtraReports.UI.XRTable table2; - private DevExpress.XtraReports.UI.XRTableRow tableRow2; - private DevExpress.XtraReports.UI.XRTableCell tableCell4; - private DevExpress.XtraReports.UI.XRTableCell tableCell5; - private DevExpress.XtraReports.UI.XRTableCell tableCell6; - private DevExpress.XtraReports.UI.XRPictureBox pictureBox1; - private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; - private DevExpress.XtraReports.UI.XRPageInfo pageInfo2; - private DevExpress.DataAccess.Sql.SqlDataSource sqlDataSource1; - private DevExpress.XtraReports.UI.XRControlStyle Title; - private DevExpress.XtraReports.UI.XRControlStyle DetailCaption1; - private DevExpress.XtraReports.UI.XRControlStyle DetailData1; - private DevExpress.XtraReports.UI.XRControlStyle DetailData3_Odd; - private DevExpress.XtraReports.UI.XRControlStyle PageInfo; - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ServerApp.PredefinedReports { + + public partial class TestReport : DevExpress.XtraReports.UI.XtraReport { + private void InitializeComponent() { + DevExpress.XtraReports.ReportInitializer reportInitializer = new DevExpress.XtraReports.ReportInitializer(this, "ServerApp.PredefinedReports.TestReport.repx"); + + // Controls + this.TopMargin = reportInitializer.GetControl("TopMargin"); + this.ReportHeader = reportInitializer.GetControl("ReportHeader"); + this.GroupHeader1 = reportInitializer.GetControl("GroupHeader1"); + this.Detail = reportInitializer.GetControl("Detail"); + this.BottomMargin = reportInitializer.GetControl("BottomMargin"); + this.label1 = reportInitializer.GetControl("label1"); + this.table1 = reportInitializer.GetControl("table1"); + this.tableRow1 = reportInitializer.GetControl("tableRow1"); + this.tableCell1 = reportInitializer.GetControl("tableCell1"); + this.tableCell2 = reportInitializer.GetControl("tableCell2"); + this.tableCell3 = reportInitializer.GetControl("tableCell3"); + this.table2 = reportInitializer.GetControl("table2"); + this.tableRow2 = reportInitializer.GetControl("tableRow2"); + this.tableCell4 = reportInitializer.GetControl("tableCell4"); + this.tableCell5 = reportInitializer.GetControl("tableCell5"); + this.tableCell6 = reportInitializer.GetControl("tableCell6"); + this.pictureBox1 = reportInitializer.GetControl("pictureBox1"); + this.pageInfo1 = reportInitializer.GetControl("pageInfo1"); + this.pageInfo2 = reportInitializer.GetControl("pageInfo2"); + + // Data Sources + this.sqlDataSource1 = reportInitializer.GetDataSource("sqlDataSource1"); + + // Styles + this.Title = reportInitializer.GetStyle("Title"); + this.DetailCaption1 = reportInitializer.GetStyle("DetailCaption1"); + this.DetailData1 = reportInitializer.GetStyle("DetailData1"); + this.DetailData3_Odd = reportInitializer.GetStyle("DetailData3_Odd"); + this.PageInfo = reportInitializer.GetStyle("PageInfo"); + } + private DevExpress.XtraReports.UI.TopMarginBand TopMargin; + private DevExpress.XtraReports.UI.ReportHeaderBand ReportHeader; + private DevExpress.XtraReports.UI.GroupHeaderBand GroupHeader1; + private DevExpress.XtraReports.UI.DetailBand Detail; + private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; + private DevExpress.XtraReports.UI.XRLabel label1; + private DevExpress.XtraReports.UI.XRTable table1; + private DevExpress.XtraReports.UI.XRTableRow tableRow1; + private DevExpress.XtraReports.UI.XRTableCell tableCell1; + private DevExpress.XtraReports.UI.XRTableCell tableCell2; + private DevExpress.XtraReports.UI.XRTableCell tableCell3; + private DevExpress.XtraReports.UI.XRTable table2; + private DevExpress.XtraReports.UI.XRTableRow tableRow2; + private DevExpress.XtraReports.UI.XRTableCell tableCell4; + private DevExpress.XtraReports.UI.XRTableCell tableCell5; + private DevExpress.XtraReports.UI.XRTableCell tableCell6; + private DevExpress.XtraReports.UI.XRPictureBox pictureBox1; + private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; + private DevExpress.XtraReports.UI.XRPageInfo pageInfo2; + private DevExpress.DataAccess.Sql.SqlDataSource sqlDataSource1; + private DevExpress.XtraReports.UI.XRControlStyle Title; + private DevExpress.XtraReports.UI.XRControlStyle DetailCaption1; + private DevExpress.XtraReports.UI.XRControlStyle DetailData1; + private DevExpress.XtraReports.UI.XRControlStyle DetailData3_Odd; + private DevExpress.XtraReports.UI.XRControlStyle PageInfo; + } +} diff --git a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.cs b/ServerApp/PredefinedReports/TestReport.cs similarity index 68% rename from dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.cs rename to ServerApp/PredefinedReports/TestReport.cs index ea89e09..4c1b1c2 100644 --- a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.cs +++ b/ServerApp/PredefinedReports/TestReport.cs @@ -1,13 +1,13 @@ -using System; -using DevExpress.XtraReports.UI; - -namespace dxSampleReactReportingPrintWithoutPreview.PredefinedReports -{ - public partial class TestReport - { - public TestReport() - { - InitializeComponent(); - } - } -} +using System; +using DevExpress.XtraReports.UI; + +namespace ServerApp.PredefinedReports +{ + public partial class TestReport + { + public TestReport() + { + InitializeComponent(); + } + } +} diff --git a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.repx b/ServerApp/PredefinedReports/TestReport.repx similarity index 98% rename from dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.repx rename to ServerApp/PredefinedReports/TestReport.repx index d415451..cd79a0e 100644 --- a/dxSampleReactReportingPrintWithoutPreview/PredefinedReports/TestReport.repx +++ b/ServerApp/PredefinedReports/TestReport.repx @@ -1,79 +1,79 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ServerApp/PredefinedReports/TestReport.resx b/ServerApp/PredefinedReports/TestReport.resx new file mode 100644 index 0000000..e36faff --- /dev/null +++ b/ServerApp/PredefinedReports/TestReport.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + PERhdGFTZXQgTmFtZT0ic3FsRGF0YVNvdXJjZTEiPjxWaWV3IE5hbWU9IkNhdGVnb3JpZXMiPjxGaWVsZCBOYW1lPSJDYXRlZ29yeUlEIiBUeXBlPSJJbnQ2NCIgLz48RmllbGQgTmFtZT0iQ2F0ZWdvcnlOYW1lIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IkRlc2NyaXB0aW9uIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IlBpY3R1cmUiIFR5cGU9IkJ5dGVBcnJheSIgLz48RmllbGQgTmFtZT0iSWNvbjE3IiBUeXBlPSJCeXRlQXJyYXkiIC8+PEZpZWxkIE5hbWU9Ikljb24yNSIgVHlwZT0iQnl0ZUFycmF5IiAvPjwvVmlldz48L0RhdGFTZXQ+ + + \ No newline at end of file diff --git a/ServerApp/Program.cs b/ServerApp/Program.cs new file mode 100644 index 0000000..3d19a94 --- /dev/null +++ b/ServerApp/Program.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using DevExpress.AspNetCore; +using DevExpress.AspNetCore.Reporting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using DevExpress.XtraReports.Web.Extensions; +using DevExpress.Security.Resources; +using Microsoft.EntityFrameworkCore; +using ServerApp.Services; +using ServerApp.Data; + +var builder = WebApplication.CreateBuilder(args); + +AppDomain.CurrentDomain.SetData("DataDirectory", builder.Environment.ContentRootPath); +builder.Services.AddDevExpressControls(); +builder.Services.AddScoped(); +builder.Services.AddMvc(); +builder.Services.ConfigureReportingServices(configurator => { + if(builder.Environment.IsDevelopment()) + configurator.UseDevelopmentMode(); + + configurator.ConfigureReportDesigner(designerConfigurator => { + designerConfigurator.RegisterDataSourceWizardConfigFileConnectionStringsProvider(); + + }); + configurator.ConfigureWebDocumentViewer(viewerConfigurator => { + viewerConfigurator.UseCachedReportSourceBuilder(); + }); +}); +builder.Services.AddDbContext(options => options.UseSqlite(builder.Configuration.GetConnectionString("ReportsDataConnectionString"))); + +builder.Services.AddCors(options => { + options.AddPolicy("AllowCorsPolicy", builder => { + // Allow all ports on local host. + builder.SetIsOriginAllowed(origin => new Uri(origin).Host == "localhost"); + builder.AllowAnyHeader(); + builder.AllowAnyMethod(); + }); +}); +var app = builder.Build(); +using(var scope = app.Services.CreateScope()) { + var services = scope.ServiceProvider; + services.GetService().InitializeDatabase(); +} +var contentDirectoryAllowRule = DirectoryAccessRule.Allow(new DirectoryInfo(Path.Combine(app.Environment.ContentRootPath, "Content")).FullName); +AccessSettings.ReportingSpecificResources.TrySetRules(contentDirectoryAllowRule, UrlAccessRule.Allow()); +DevExpress.XtraReports.Configuration.Settings.Default.UserDesignerOptions.DataBindingMode = DevExpress.XtraReports.UI.DataBindingMode.Expressions; + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseCors("AllowCorsPolicy"); + +app.UseDevExpressControls(); +System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12; +app.MapControllerRoute( + name: "default", + pattern: "{controller}/{action=Index}/{id?}"); + + +app.Run(); \ No newline at end of file diff --git a/ServerApp/Properties/launchSettings.json b/ServerApp/Properties/launchSettings.json new file mode 100644 index 0000000..289cc86 --- /dev/null +++ b/ServerApp/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "ServerApp": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44335;http://localhost:13781" + } + } +} diff --git a/ServerApp/ServerApp.csproj b/ServerApp/ServerApp.csproj new file mode 100644 index 0000000..e9ba2d4 --- /dev/null +++ b/ServerApp/ServerApp.csproj @@ -0,0 +1,40 @@ + + + + net8.0 + enable + true + + + + + + + + + + + + + + + + TestReport.repx + + + TestReport.repx + + + + + + + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/ServerApp/ServerApp.sln b/ServerApp/ServerApp.sln new file mode 100644 index 0000000..134f041 --- /dev/null +++ b/ServerApp/ServerApp.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerApp", "ServerApp.csproj", "{933F1940-34C7-1FE1-951C-31F7F1886CF1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {933F1940-34C7-1FE1-951C-31F7F1886CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {933F1940-34C7-1FE1-951C-31F7F1886CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {933F1940-34C7-1FE1-951C-31F7F1886CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {933F1940-34C7-1FE1-951C-31F7F1886CF1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B8F6522D-274E-4800-A7FA-D4BDB02AD133} + EndGlobalSection +EndGlobal diff --git a/dxSampleReactReportingPrintWithoutPreview/Services/ReportStorageWebExtension.cs b/ServerApp/Services/ReportStorageWebExtension.cs similarity index 95% rename from dxSampleReactReportingPrintWithoutPreview/Services/ReportStorageWebExtension.cs rename to ServerApp/Services/ReportStorageWebExtension.cs index b0eb0cf..a44bb12 100644 --- a/dxSampleReactReportingPrintWithoutPreview/Services/ReportStorageWebExtension.cs +++ b/ServerApp/Services/ReportStorageWebExtension.cs @@ -1,96 +1,96 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using DevExpress.XtraReports.Web.Extensions; -using DevExpress.XtraReports.UI; -using Microsoft.AspNetCore.Hosting; -using dxSampleReactReportingPrintWithoutPreview.PredefinedReports; - -namespace dxSampleReactReportingPrintWithoutPreview.Services -{ - public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension - { - readonly string ReportDirectory; - const string FileExtension = ".repx"; - public CustomReportStorageWebExtension(IWebHostEnvironment env) { - ReportDirectory = Path.Combine(env.ContentRootPath, "Reports"); - if (!Directory.Exists(ReportDirectory)) { - Directory.CreateDirectory(ReportDirectory); - } - } - - private bool IsWithinReportsFolder(string url, string folder) { - var rootDirectory = new DirectoryInfo(folder); - var fileInfo = new FileInfo(Path.Combine(folder, url)); - return fileInfo.Directory.FullName.ToLower().StartsWith(rootDirectory.FullName.ToLower()); - } - - public override bool CanSetData(string url) { - // Determines whether or not it is possible to store a report by a given URL. - // For instance, make the CanSetData method return false for reports that should be read-only in your storage. - // This method is called only for valid URLs (i.e., if the IsValidUrl method returned true) before the SetData method is called. - - return true; - } - - public override bool IsValidUrl(string url) { - // Determines whether or not the URL passed to the current Report Storage is valid. - // For instance, implement your own logic to prohibit URLs that contain white spaces or some other special characters. - // This method is called before the CanSetData and GetData methods. - - return Path.GetFileName(url) == url; - } - - public override byte[] GetData(string url) { - // Returns report layout data stored in a Report Storage using the specified URL. - // This method is called only for valid URLs after the IsValidUrl method is called. - try { - if (Directory.EnumerateFiles(ReportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url)) - { - return File.ReadAllBytes(Path.Combine(ReportDirectory, url + FileExtension)); - } - if (ReportsFactory.Reports.ContainsKey(url)) - { - using (MemoryStream ms = new MemoryStream()) { - ReportsFactory.Reports[url]().SaveLayoutToXml(ms); - return ms.ToArray(); - } - } - } catch (Exception ex) { - throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Could not get report data.", ex); - } - throw new DevExpress.XtraReports.Web.ClientControls.FaultException(string.Format("Could not find report '{0}'.", url)); - } - - public override Dictionary GetUrls() { - // Returns a dictionary of the existing report URLs and display names. - // This method is called when running the Report Designer, - // before the Open Report and Save Report dialogs are shown and after a new report is saved to a storage. - - return Directory.GetFiles(ReportDirectory, "*" + FileExtension) - .Select(Path.GetFileNameWithoutExtension) - .Union(ReportsFactory.Reports.Select(x => x.Key)) - .ToDictionary(x => x); - } - - public override void SetData(XtraReport report, string url) { - // Stores the specified report to a Report Storage using the specified URL. - // This method is called only after the IsValidUrl and CanSetData methods are called. - if(!IsWithinReportsFolder(url, ReportDirectory)) - throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Invalid report name."); - report.SaveLayoutToXml(Path.Combine(ReportDirectory, url + FileExtension)); - } - - public override string SetNewData(XtraReport report, string defaultUrl) { - // Stores the specified report using a new URL. - // The IsValidUrl and CanSetData methods are never called before this method. - // You can validate and correct the specified URL directly in the SetNewData method implementation - // and return the resulting URL used to save a report in your storage. - SetData(report, defaultUrl); - return defaultUrl; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using DevExpress.XtraReports.Web.Extensions; +using DevExpress.XtraReports.UI; +using Microsoft.AspNetCore.Hosting; +using ServerApp.PredefinedReports; + +namespace ServerApp.Services +{ + public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension + { + readonly string ReportDirectory; + const string FileExtension = ".repx"; + public CustomReportStorageWebExtension(IWebHostEnvironment env) { + ReportDirectory = Path.Combine(env.ContentRootPath, "Reports"); + if (!Directory.Exists(ReportDirectory)) { + Directory.CreateDirectory(ReportDirectory); + } + } + + private bool IsWithinReportsFolder(string url, string folder) { + var rootDirectory = new DirectoryInfo(folder); + var fileInfo = new FileInfo(Path.Combine(folder, url)); + return fileInfo.Directory.FullName.ToLower().StartsWith(rootDirectory.FullName.ToLower()); + } + + public override bool CanSetData(string url) { + // Determines whether or not it is possible to store a report by a given URL. + // For instance, make the CanSetData method return false for reports that should be read-only in your storage. + // This method is called only for valid URLs (i.e., if the IsValidUrl method returned true) before the SetData method is called. + + return true; + } + + public override bool IsValidUrl(string url) { + // Determines whether or not the URL passed to the current Report Storage is valid. + // For instance, implement your own logic to prohibit URLs that contain white spaces or some other special characters. + // This method is called before the CanSetData and GetData methods. + + return Path.GetFileName(url) == url; + } + + public override byte[] GetData(string url) { + // Returns report layout data stored in a Report Storage using the specified URL. + // This method is called only for valid URLs after the IsValidUrl method is called. + try { + if (Directory.EnumerateFiles(ReportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url)) + { + return File.ReadAllBytes(Path.Combine(ReportDirectory, url + FileExtension)); + } + if (ReportsFactory.Reports.ContainsKey(url)) + { + using (MemoryStream ms = new MemoryStream()) { + ReportsFactory.Reports[url]().SaveLayoutToXml(ms); + return ms.ToArray(); + } + } + } catch (Exception ex) { + throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Could not get report data.", ex); + } + throw new DevExpress.XtraReports.Web.ClientControls.FaultException(string.Format("Could not find report '{0}'.", url)); + } + + public override Dictionary GetUrls() { + // Returns a dictionary of the existing report URLs and display names. + // This method is called when running the Report Designer, + // before the Open Report and Save Report dialogs are shown and after a new report is saved to a storage. + + return Directory.GetFiles(ReportDirectory, "*" + FileExtension) + .Select(Path.GetFileNameWithoutExtension) + .Union(ReportsFactory.Reports.Select(x => x.Key)) + .ToDictionary(x => x); + } + + public override void SetData(XtraReport report, string url) { + // Stores the specified report to a Report Storage using the specified URL. + // This method is called only after the IsValidUrl and CanSetData methods are called. + if(!IsWithinReportsFolder(url, ReportDirectory)) + throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Invalid report name."); + report.SaveLayoutToXml(Path.Combine(ReportDirectory, url + FileExtension)); + } + + public override string SetNewData(XtraReport report, string defaultUrl) { + // Stores the specified report using a new URL. + // The IsValidUrl and CanSetData methods are never called before this method. + // You can validate and correct the specified URL directly in the SetNewData method implementation + // and return the resulting URL used to save a report in your storage. + SetData(report, defaultUrl); + return defaultUrl; + } + } +} diff --git a/ServerApp/appsettings.Development.json b/ServerApp/appsettings.Development.json new file mode 100644 index 0000000..cc7fe77 --- /dev/null +++ b/ServerApp/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "System": "Information", + "Microsoft": "Information", + "DevExpress": "Information" + } + } +} diff --git a/ServerApp/appsettings.json b/ServerApp/appsettings.json new file mode 100644 index 0000000..2f226ec --- /dev/null +++ b/ServerApp/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "DevExpress": "Warning" + } + }, + "ConnectionStrings": { + "NWindConnectionString": "XpoProvider=SQLite;Data Source=|DataDirectory|Data/nwind.db", + "ReportsDataConnectionString": "Filename=Data/reportsData.db" + } +} diff --git a/dxSampleReactReportingPrintWithoutPreview/.gitignore b/dxSampleReactReportingPrintWithoutPreview/.gitignore deleted file mode 100644 index 67e842d..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/.gitignore +++ /dev/null @@ -1,232 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -bin/ -Bin/ -obj/ -Obj/ - -# Visual Studio 2015 cache/options directory -.vs/ -/wwwroot/dist/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory -AppPackages/ -BundleArtifacts/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -orleans.codegen.cs - -/node_modules - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe - -# FAKE - F# Make -.fake/ diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/.gitignore b/dxSampleReactReportingPrintWithoutPreview/ClientApp/.gitignore deleted file mode 100644 index 69e588b..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. - -# dependencies -/node_modules - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -package-lock.json diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/package.json b/dxSampleReactReportingPrintWithoutPreview/ClientApp/package.json deleted file mode 100644 index 811faad..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "dxsamplereactreportingprintwithoutpreview", - "version": "0.1.0", - "private": true, - "dependencies": { - "@devexpress/analytics-core": "24.2-stable", - "bootstrap": "^4.1.3", - "devexpress-reporting": "24.2-stable", - "devextreme": "24.2-stable", - "devextreme-react": "24.2-stable", - "file-saver": "^2.0.2", - "jquery": "^3.4.1", - "merge": "^2.1.1", - "oidc-client": "^1.9.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "5.0.1", - "react-router-bootstrap": "^0.26.2", - "react-router-dom": "^6.11.2", - "web-vitals": "^2.1.4" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/favicon.ico b/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/favicon.ico deleted file mode 100644 index a3a7999..0000000 Binary files a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/favicon.ico and /dev/null differ diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/index.html b/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/index.html deleted file mode 100644 index 64934fc..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - DevExpress Reporting Sample - - - - -
DevExpress Reporting Sample: Print Reports Without Preview
-
- - - diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/manifest.json b/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/manifest.json deleted file mode 100644 index b50a0b1..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "dxSampleReactReportingPrintWithoutPreview", - "name": "dxSampleReactReportingPrintWithoutPreview", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": "./index.html", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.test.js b/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.test.js deleted file mode 100644 index 2d26145..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { MemoryRouter } from 'react-router-dom'; -import App from './App'; - -it('renders without crashing', async () => { - const div = document.createElement('div'); - ReactDOM.render( - - - , div); - await new Promise(resolve => setTimeout(resolve, 1000)); -}); diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/components/HomeComponent.jsx b/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/components/HomeComponent.jsx deleted file mode 100644 index d100eca..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/components/HomeComponent.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import React from "react"; -import saveAs from "file-saver"; - - -class HomeComponent extends React.Component { - constructor(props) { - super(props); - - this.selectedFormat = 'pdf'; - this.printUrl = ""; - - this.reportUrl = "TestReport"; - - this.onChange = this.onChange.bind(this); - this.downloadFile = this.downloadFile.bind(this); - this.printInNewWindow = this.printInNewWindow.bind(this); - this.printInIframe = this.printInIframe.bind(this); - }; - - onChange(event) { - this.selectedFormat = event.target.value; - } - - printInNewWindow() { - var frameElement = window.open("api/Home/Print", "_blank"); - frameElement.addEventListener("load", function (e) { - if (frameElement.document.contentType !== "text/html") - frameElement.print(); - }); - } - - printInIframe() { - var iframe = document.getElementById('printFrame'); - if (iframe.contentDocument.contentType !== "text/html") - iframe.contentWindow.print(); - } - - downloadFile() { - fetch("api/Home/Export?format=" + this.selectedFormat) - .then(response => response.blob()) - .then(data => { - saveAs(data, 'TestReport.' + this.selectedFormat.toLowerCase()); - }); - - } - - render() { - - return ( -
- - - - - -
- ); - } - componentDidMount() { - - } - componentWillUnmount() { - - } -}; - -export default HomeComponent; diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/custom.css b/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/custom.css deleted file mode 100644 index 24a6ac4..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/custom.css +++ /dev/null @@ -1,14 +0,0 @@ -/* Provide sufficient contrast against white background */ -a { - color: #0366d6; -} - -code { - color: #E01A76; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/index.js b/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/index.js deleted file mode 100644 index 3ed9e66..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import 'bootstrap/dist/css/bootstrap.css'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { BrowserRouter } from 'react-router-dom'; -import App from './App'; -import registerServiceWorker from './registerServiceWorker'; - -const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href'); -const rootElement = document.getElementById('root'); - -ReactDOM.render( - - - , - rootElement); - -registerServiceWorker(); - diff --git a/dxSampleReactReportingPrintWithoutPreview/Controllers/ReportingControllers.cs b/dxSampleReactReportingPrintWithoutPreview/Controllers/ReportingControllers.cs deleted file mode 100644 index 1b6e765..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/Controllers/ReportingControllers.cs +++ /dev/null @@ -1,23 +0,0 @@ -using DevExpress.AspNetCore.Reporting.QueryBuilder; -using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services; -using DevExpress.AspNetCore.Reporting.ReportDesigner; -using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer; -using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; - -namespace dxSampleReactReportingPrintWithoutPreview.Controllers { - public class CustomWebDocumentViewerController : WebDocumentViewerController { - public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService) { - } - } - - public class CustomReportDesignerController : ReportDesignerController { - public CustomReportDesignerController(IReportDesignerMvcControllerService controllerService) : base(controllerService) { - } - } - - public class CustomQueryBuilderController : QueryBuilderController { - public CustomQueryBuilderController(IQueryBuilderMvcControllerService controllerService) : base(controllerService) { - } - } -} diff --git a/dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml b/dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml deleted file mode 100644 index 09da0d2..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/Pages/Error.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@page -@model ErrorModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to the Development environment displays detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

diff --git a/dxSampleReactReportingPrintWithoutPreview/Pages/_ViewImports.cshtml b/dxSampleReactReportingPrintWithoutPreview/Pages/_ViewImports.cshtml deleted file mode 100644 index 3f481f3..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using dxSampleReactReportingPrintWithoutPreview -@namespace dxSampleReactReportingPrintWithoutPreview.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/dxSampleReactReportingPrintWithoutPreview/Program.cs b/dxSampleReactReportingPrintWithoutPreview/Program.cs deleted file mode 100644 index 68e6070..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace dxSampleReactReportingPrintWithoutPreview -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/dxSampleReactReportingPrintWithoutPreview/Startup.cs b/dxSampleReactReportingPrintWithoutPreview/Startup.cs deleted file mode 100644 index 0afbe04..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/Startup.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using DevExpress.AspNetCore; -using DevExpress.AspNetCore.Reporting; -using Microsoft.AspNetCore.SpaServices.AngularCli; -using DevExpress.XtraReports.Web.Extensions; -using dxSampleReactReportingPrintWithoutPreview.Services; -using DevExpress.Security.Resources; -using System.IO; - -namespace dxSampleReactReportingPrintWithoutPreview -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - - services.AddDevExpressControls(); - services.AddScoped(); - services - .AddControllersWithViews() - .AddNewtonsoftJson(); - services.ConfigureReportingServices(configurator => { - configurator.ConfigureReportDesigner(designerConfigurator => { - designerConfigurator.RegisterDataSourceWizardConfigFileConnectionStringsProvider(); - }); - configurator.ConfigureWebDocumentViewer(viewerConfigurator => { - viewerConfigurator.UseCachedReportSourceBuilder(); - }); - }); - - // In production, the React files will be served from this directory - services.AddSpaStaticFiles(configuration => - { - configuration.RootPath = "ClientApp/build"; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - var contentDirectoryAllowRule = DirectoryAccessRule.Allow(new DirectoryInfo(Path.Combine(env.ContentRootPath, "..", "Content")).FullName); - AccessSettings.ReportingSpecificResources.TrySetRules(contentDirectoryAllowRule, UrlAccessRule.Allow()); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - if (!env.IsDevelopment()) { - app.UseSpaStaticFiles(); - } - - app.UseRouting(); - - app.UseDevExpressControls(); - - System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12; - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller}/{action=Index}/{id?}"); - }); - - app.UseSpa(spa => - { - spa.Options.SourcePath = "ClientApp"; - - if (env.IsDevelopment()) - { - spa.UseReactDevelopmentServer(npmScript: "start"); - } - }); - } - } -} diff --git a/dxSampleReactReportingPrintWithoutPreview/appsettings.Development.json b/dxSampleReactReportingPrintWithoutPreview/appsettings.Development.json deleted file mode 100644 index dba68eb..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/dxSampleReactReportingPrintWithoutPreview/appsettings.json b/dxSampleReactReportingPrintWithoutPreview/appsettings.json deleted file mode 100644 index b07cc57..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "ConnectionStrings": { - "NWindConnectionString": "XpoProvider=SQLite;Data Source=Data/nwind.db" - }, - "AllowedHosts": "*" -} diff --git a/dxSampleReactReportingPrintWithoutPreview/dxSampleReactReportingPrintWithoutPreview.csproj b/dxSampleReactReportingPrintWithoutPreview/dxSampleReactReportingPrintWithoutPreview.csproj deleted file mode 100644 index 10dd1a6..0000000 --- a/dxSampleReactReportingPrintWithoutPreview/dxSampleReactReportingPrintWithoutPreview.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - - net8.0 - true - Latest - false - ClientApp\ - $(DefaultItemExcludes);$(SpaRoot)node_modules\** - - - - - - - - - - - - - - - - - - - - - - TestReport.repx - - - TestReport.repx - - - - - - - - - - - - - - - - - - - - %(DistFiles.Identity) - PreserveNewest - true - - - - \ No newline at end of file diff --git a/react-app/.gitignore b/react-app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/react-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/react-app/README.md b/react-app/README.md new file mode 100644 index 0000000..7059a96 --- /dev/null +++ b/react-app/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/react-app/eslint.config.js b/react-app/eslint.config.js new file mode 100644 index 0000000..cee1e2c --- /dev/null +++ b/react-app/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/react-app/index.html b/react-app/index.html new file mode 100644 index 0000000..0c589ec --- /dev/null +++ b/react-app/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/react-app/package.json b/react-app/package.json new file mode 100644 index 0000000..cc6580a --- /dev/null +++ b/react-app/package.json @@ -0,0 +1,29 @@ +{ + "name": "react-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "file-saver": "^2.0.2", + "devexpress-reporting-react": "24.2-stable" + }, + "devDependencies": { + "@eslint/js": "^9.29.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.5.2", + "eslint": "^9.29.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.2.0", + "vite": "^7.0.0" + } +} diff --git a/react-app/public/vite.svg b/react-app/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/react-app/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/react-app/src/App.css b/react-app/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/react-app/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.js b/react-app/src/App.jsx similarity index 64% rename from dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.js rename to react-app/src/App.jsx index 30f1b77..7328c09 100644 --- a/dxSampleReactReportingPrintWithoutPreview/ClientApp/src/App.js +++ b/react-app/src/App.jsx @@ -1,18 +1,19 @@ -import React, { Component } from 'react'; -import HomeComponent from "./components/HomeComponent"; - -import './custom.css' - -export default class App extends Component { - static displayName = App.name; - - render() { - return ( -
-
- -
-
- ); - } -} +import { useState, Component } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' +import HomeComponent from "./components/HomeComponent"; + +export default class App extends Component { + static displayName = App.name; + + render() { + return ( +
+
+ +
+
+ ); + } +} diff --git a/react-app/src/assets/react.svg b/react-app/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/react-app/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/react-app/src/components/HomeComponent.jsx b/react-app/src/components/HomeComponent.jsx new file mode 100644 index 0000000..87b3d41 --- /dev/null +++ b/react-app/src/components/HomeComponent.jsx @@ -0,0 +1,69 @@ +import React, { useRef, useState } from 'react'; +import { saveAs } from 'file-saver'; + +export default function HomeComponent() { + let selectedFormat = 'PDF'; + const iframeRef = useRef(); + + const downloadFile = () => { + fetch(`/api/Home/Export?format=${selectedFormat}`) + .then(response => { + if (!response.ok) { + throw new Error('An error has occurred.'); + } + return response.blob();}) + .then(data => { + saveAs(data, 'TestReport.' + selectedFormat.toLowerCase()); + }) + .catch(error => { + console.error('An error has occurred:', error); + }); + }; + + const printInNewTab = () => { + var frameElement = window.open("api/Home/Print", "_blank"); + frameElement?.addEventListener("load", function (e) { + if (frameElement.document.contentType !== "text/html") + frameElement.print(); + }); + }; + + const printInIframe = () => { + const iframe = iframeRef.current; + if (!iframe) { + console.error('IFrame not found'); + return; + } + try { + if (iframe.contentDocument?.contentType !== "text/html") { + iframe.contentWindow.print(); + } + } catch (error) { + console.error('An error has occurred::', error); + } + }; + + + return ( +
+
+ + + + +
+