From 9a69b818bd4d5b432183b10aeb01f68b12265592 Mon Sep 17 00:00:00 2001 From: merdan Date: Mon, 2 Sep 2024 15:07:25 +0500 Subject: [PATCH] Add project files. --- .filenesting.json | 3 + .../.config/dotnet-tools.json | 13 + .../API/!!!Feedback/Readme.txt | 21 + .../API/FileAttachments/Readme.txt | 16 + .../API/Localization/Readme.txt | 19 + .../API/Reports/Readme.txt | 17 + .../API/Reports/ReportController.cs | 87 +++ .../API/Security/AuthenticationController.cs | 32 + .../API/Security/JwtTokenProviderService.cs | 37 + .../API/Security/Readme.txt | 21 + DurnyklyYol.Blazor.Server/App.razor | 11 + .../BlazorApplication.cs | 49 ++ DurnyklyYol.Blazor.Server/BlazorModule.cs | 38 ++ .../Controllers/CargoController.cs | 86 +++ .../Controllers/ClientController.cs | 44 ++ .../Controllers/GoodsController.cs | 81 +++ .../GoodsImageUploadController.Designer.cs | 36 + .../Controllers/GoodsImageUploadController.cs | 57 ++ .../Controllers/ReadMe.txt | 25 + DurnyklyYol.Blazor.Server/DTO/CargoDto.cs | 11 + DurnyklyYol.Blazor.Server/DTO/GoodsListDto.cs | 31 + DurnyklyYol.Blazor.Server/DTO/PointDto.cs | 12 + .../DurnyklyYol.Blazor.Server.csproj | 56 ++ .../Editors/GoodsImageUploadPropertyEditor.cs | 17 + DurnyklyYol.Blazor.Server/Editors/ReadMe.txt | 25 + .../GoodsImageUpload.razor | 40 ++ DurnyklyYol.Blazor.Server/Images/ReadMe.txt | 12 + DurnyklyYol.Blazor.Server/Model.xafml | 48 ++ DurnyklyYol.Blazor.Server/Model_tk-TM.xafml | 2 + .../OData/MyODataQueryValidator.cs | 14 + DurnyklyYol.Blazor.Server/Pages/_Host.cshtml | 57 ++ DurnyklyYol.Blazor.Server/Program.cs | 52 ++ .../Properties/launchSettings.json | 27 + DurnyklyYol.Blazor.Server/ReadMe.txt | 40 ++ .../Services/CircuitHandlerProxy.cs | 23 + .../Services/ProxyHubConnectionHandler.cs | 27 + DurnyklyYol.Blazor.Server/Startup.cs | 210 ++++++ DurnyklyYol.Blazor.Server/_Imports.razor | 11 + .../appsettings.Development.json | 16 + DurnyklyYol.Blazor.Server/appsettings.json | 62 ++ .../wwwroot/css/site.css | 31 + DurnyklyYol.Blazor.Server/wwwroot/favicon.ico | Bin 0 -> 31444 bytes .../wwwroot/images/Logo.svg | 50 ++ .../wwwroot/images/LogoExpress.svg | 41 ++ .../wwwroot/images/SplashScreen.svg | 17 + .../wwwroot/images/SplashScreenXAF.svg | 11 + .../DurnyklyYol.MiddleTier.csproj | 27 + .../JWT/JwtTokenProviderService.cs | 37 + .../JWT/WebApiAuthenticationController.cs | 29 + DurnyklyYol.MiddleTier/Program.cs | 50 ++ .../Properties/launchSettings.json | 30 + DurnyklyYol.MiddleTier/ReadMe.txt | 15 + DurnyklyYol.MiddleTier/Startup.cs | 125 ++++ .../appsettings.Development.json | 16 + DurnyklyYol.MiddleTier/appsettings.json | 29 + .../BusinessObjects/ApplicationUser.cs | 45 ++ .../ApplicationUserLoginInfo.cs | 36 + DurnyklyYol.Module/BusinessObjects/Cargo.cs | 208 ++++++ .../BusinessObjects/CargoRoutePoints.cs | 54 ++ DurnyklyYol.Module/BusinessObjects/Carrier.cs | 86 +++ DurnyklyYol.Module/BusinessObjects/Client.cs | 104 +++ DurnyklyYol.Module/BusinessObjects/Contact.cs | 51 ++ DurnyklyYol.Module/BusinessObjects/Expense.cs | 98 +++ DurnyklyYol.Module/BusinessObjects/Goods.cs | 241 +++++++ .../BusinessObjects/GoodsImage.cs | 39 ++ DurnyklyYol.Module/BusinessObjects/Payment.cs | 95 +++ DurnyklyYol.Module/BusinessObjects/Point.cs | 58 ++ DurnyklyYol.Module/BusinessObjects/ReadMe.txt | 26 + DurnyklyYol.Module/BusinessObjects/Region.cs | 41 ++ DurnyklyYol.Module/BusinessObjects/Route.cs | 61 ++ .../BusinessObjects/RoutePoint.cs | 60 ++ DurnyklyYol.Module/BusinessObjects/Shop.cs | 61 ++ .../BusinessObjects/Warehouse.cs | 64 ++ .../Controllers/GoodsController.Designer.cs | 36 + .../Controllers/GoodsController.cs | 53 ++ DurnyklyYol.Module/Controllers/ReadMe.txt | 25 + DurnyklyYol.Module/DatabaseUpdate/ReadMe.txt | 23 + DurnyklyYol.Module/DatabaseUpdate/Updater.cs | 116 ++++ DurnyklyYol.Module/DurnyklyYol.Module.csproj | 51 ++ DurnyklyYol.Module/GlobalConstants.cs | 14 + DurnyklyYol.Module/Images/ReadMe.txt | 12 + ...del.DesignedDiffs.Localization.tk-TM.xafml | 181 +++++ DurnyklyYol.Module/Model.DesignedDiffs.xafml | 201 ++++++ DurnyklyYol.Module/Module.cs | 53 ++ DurnyklyYol.Module/ReadMe.txt | 32 + DurnyklyYol.Module/UnusableNodes.xml | 23 + DurnyklyYol.Module/UnusableNodes_tk-TM.xml | 24 + DurnyklyYol.Module/Welcome.html | 192 ++++++ DurnyklyYol.Win/App.config | 24 + DurnyklyYol.Win/Controllers/ReadMe.txt | 25 + DurnyklyYol.Win/DurnyklyYol.Win.csproj | 50 ++ DurnyklyYol.Win/Editors/ReadMe.txt | 46 ++ DurnyklyYol.Win/ExpressApp.ico | Bin 0 -> 113407 bytes DurnyklyYol.Win/Images/ExpressAppLogo.png | Bin 0 -> 7486 bytes DurnyklyYol.Win/Images/Logo.svg | 16 + DurnyklyYol.Win/Images/ReadMe.txt | 12 + DurnyklyYol.Win/Model.xafml | 4 + DurnyklyYol.Win/Program.cs | 51 ++ DurnyklyYol.Win/ReadMe.txt | 33 + DurnyklyYol.Win/Startup.cs | 77 +++ DurnyklyYol.Win/WinApplication.cs | 43 ++ DurnyklyYol.Win/WinModule.cs | 37 + DurnyklyYol.Win/XafSplashScreen.Designer.cs | 181 +++++ DurnyklyYol.Win/XafSplashScreen.cs | 54 ++ DurnyklyYol.Win/XafSplashScreen.resx | 638 ++++++++++++++++++ DurnyklyYol.sln | 60 ++ .../BusinessObjects/FileSystemLinkObject.cs | 62 ++ .../BusinessObjects/FileSystemStoreObject.cs | 162 +++++ FileSystemData/FileSystemData.csproj | 37 + FileSystemData/Module.Designer.cs | 31 + FileSystemData/Module.cs | 41 ++ FileSystemData/Module.resx | 120 ++++ 112 files changed, 6262 insertions(+) create mode 100644 .filenesting.json create mode 100644 DurnyklyYol.Blazor.Server/.config/dotnet-tools.json create mode 100644 DurnyklyYol.Blazor.Server/API/!!!Feedback/Readme.txt create mode 100644 DurnyklyYol.Blazor.Server/API/FileAttachments/Readme.txt create mode 100644 DurnyklyYol.Blazor.Server/API/Localization/Readme.txt create mode 100644 DurnyklyYol.Blazor.Server/API/Reports/Readme.txt create mode 100644 DurnyklyYol.Blazor.Server/API/Reports/ReportController.cs create mode 100644 DurnyklyYol.Blazor.Server/API/Security/AuthenticationController.cs create mode 100644 DurnyklyYol.Blazor.Server/API/Security/JwtTokenProviderService.cs create mode 100644 DurnyklyYol.Blazor.Server/API/Security/Readme.txt create mode 100644 DurnyklyYol.Blazor.Server/App.razor create mode 100644 DurnyklyYol.Blazor.Server/BlazorApplication.cs create mode 100644 DurnyklyYol.Blazor.Server/BlazorModule.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/CargoController.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/ClientController.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/GoodsController.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.Designer.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.cs create mode 100644 DurnyklyYol.Blazor.Server/Controllers/ReadMe.txt create mode 100644 DurnyklyYol.Blazor.Server/DTO/CargoDto.cs create mode 100644 DurnyklyYol.Blazor.Server/DTO/GoodsListDto.cs create mode 100644 DurnyklyYol.Blazor.Server/DTO/PointDto.cs create mode 100644 DurnyklyYol.Blazor.Server/DurnyklyYol.Blazor.Server.csproj create mode 100644 DurnyklyYol.Blazor.Server/Editors/GoodsImageUploadPropertyEditor.cs create mode 100644 DurnyklyYol.Blazor.Server/Editors/ReadMe.txt create mode 100644 DurnyklyYol.Blazor.Server/GoodsImageUpload.razor create mode 100644 DurnyklyYol.Blazor.Server/Images/ReadMe.txt create mode 100644 DurnyklyYol.Blazor.Server/Model.xafml create mode 100644 DurnyklyYol.Blazor.Server/Model_tk-TM.xafml create mode 100644 DurnyklyYol.Blazor.Server/OData/MyODataQueryValidator.cs create mode 100644 DurnyklyYol.Blazor.Server/Pages/_Host.cshtml create mode 100644 DurnyklyYol.Blazor.Server/Program.cs create mode 100644 DurnyklyYol.Blazor.Server/Properties/launchSettings.json create mode 100644 DurnyklyYol.Blazor.Server/ReadMe.txt create mode 100644 DurnyklyYol.Blazor.Server/Services/CircuitHandlerProxy.cs create mode 100644 DurnyklyYol.Blazor.Server/Services/ProxyHubConnectionHandler.cs create mode 100644 DurnyklyYol.Blazor.Server/Startup.cs create mode 100644 DurnyklyYol.Blazor.Server/_Imports.razor create mode 100644 DurnyklyYol.Blazor.Server/appsettings.Development.json create mode 100644 DurnyklyYol.Blazor.Server/appsettings.json create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/css/site.css create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/favicon.ico create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/images/Logo.svg create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/images/LogoExpress.svg create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreen.svg create mode 100644 DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreenXAF.svg create mode 100644 DurnyklyYol.MiddleTier/DurnyklyYol.MiddleTier.csproj create mode 100644 DurnyklyYol.MiddleTier/JWT/JwtTokenProviderService.cs create mode 100644 DurnyklyYol.MiddleTier/JWT/WebApiAuthenticationController.cs create mode 100644 DurnyklyYol.MiddleTier/Program.cs create mode 100644 DurnyklyYol.MiddleTier/Properties/launchSettings.json create mode 100644 DurnyklyYol.MiddleTier/ReadMe.txt create mode 100644 DurnyklyYol.MiddleTier/Startup.cs create mode 100644 DurnyklyYol.MiddleTier/appsettings.Development.json create mode 100644 DurnyklyYol.MiddleTier/appsettings.json create mode 100644 DurnyklyYol.Module/BusinessObjects/ApplicationUser.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/ApplicationUserLoginInfo.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Cargo.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/CargoRoutePoints.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Carrier.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Client.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Contact.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Expense.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Goods.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/GoodsImage.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Payment.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Point.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/ReadMe.txt create mode 100644 DurnyklyYol.Module/BusinessObjects/Region.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Route.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/RoutePoint.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Shop.cs create mode 100644 DurnyklyYol.Module/BusinessObjects/Warehouse.cs create mode 100644 DurnyklyYol.Module/Controllers/GoodsController.Designer.cs create mode 100644 DurnyklyYol.Module/Controllers/GoodsController.cs create mode 100644 DurnyklyYol.Module/Controllers/ReadMe.txt create mode 100644 DurnyklyYol.Module/DatabaseUpdate/ReadMe.txt create mode 100644 DurnyklyYol.Module/DatabaseUpdate/Updater.cs create mode 100644 DurnyklyYol.Module/DurnyklyYol.Module.csproj create mode 100644 DurnyklyYol.Module/GlobalConstants.cs create mode 100644 DurnyklyYol.Module/Images/ReadMe.txt create mode 100644 DurnyklyYol.Module/Model.DesignedDiffs.Localization.tk-TM.xafml create mode 100644 DurnyklyYol.Module/Model.DesignedDiffs.xafml create mode 100644 DurnyklyYol.Module/Module.cs create mode 100644 DurnyklyYol.Module/ReadMe.txt create mode 100644 DurnyklyYol.Module/UnusableNodes.xml create mode 100644 DurnyklyYol.Module/UnusableNodes_tk-TM.xml create mode 100644 DurnyklyYol.Module/Welcome.html create mode 100644 DurnyklyYol.Win/App.config create mode 100644 DurnyklyYol.Win/Controllers/ReadMe.txt create mode 100644 DurnyklyYol.Win/DurnyklyYol.Win.csproj create mode 100644 DurnyklyYol.Win/Editors/ReadMe.txt create mode 100644 DurnyklyYol.Win/ExpressApp.ico create mode 100644 DurnyklyYol.Win/Images/ExpressAppLogo.png create mode 100644 DurnyklyYol.Win/Images/Logo.svg create mode 100644 DurnyklyYol.Win/Images/ReadMe.txt create mode 100644 DurnyklyYol.Win/Model.xafml create mode 100644 DurnyklyYol.Win/Program.cs create mode 100644 DurnyklyYol.Win/ReadMe.txt create mode 100644 DurnyklyYol.Win/Startup.cs create mode 100644 DurnyklyYol.Win/WinApplication.cs create mode 100644 DurnyklyYol.Win/WinModule.cs create mode 100644 DurnyklyYol.Win/XafSplashScreen.Designer.cs create mode 100644 DurnyklyYol.Win/XafSplashScreen.cs create mode 100644 DurnyklyYol.Win/XafSplashScreen.resx create mode 100644 DurnyklyYol.sln create mode 100644 FileSystemData/BusinessObjects/FileSystemLinkObject.cs create mode 100644 FileSystemData/BusinessObjects/FileSystemStoreObject.cs create mode 100644 FileSystemData/FileSystemData.csproj create mode 100644 FileSystemData/Module.Designer.cs create mode 100644 FileSystemData/Module.cs create mode 100644 FileSystemData/Module.resx diff --git a/.filenesting.json b/.filenesting.json new file mode 100644 index 0000000..0b71966 --- /dev/null +++ b/.filenesting.json @@ -0,0 +1,3 @@ +{ + "help":"https://go.microsoft.com/fwlink/?linkid=866610" +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/.config/dotnet-tools.json b/DurnyklyYol.Blazor.Server/.config/dotnet-tools.json new file mode 100644 index 0000000..9fab42f --- /dev/null +++ b/DurnyklyYol.Blazor.Server/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "8.0.8", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/!!!Feedback/Readme.txt b/DurnyklyYol.Blazor.Server/API/!!!Feedback/Readme.txt new file mode 100644 index 0000000..a3803be --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/!!!Feedback/Readme.txt @@ -0,0 +1,21 @@ +YOUR FEEDBACK MATTERS +If you are an active Universal subscriber (or planning to become one), and are interested in additional Web API Service endpoints: + - check the state of input data with 10+ of built-in and unlimited custom validation rules; + - download PDF, RTF, etc. from Office documents or Mail Merge templates (RichTextMailMergeData) based on filtered data in databases; + - download PDF, RTF, etc. from Dashboard templates (DashboardData) based on filtered data in databases; + - etc. + +Please share your specific project requirements with us OR tell us how our Web API Service works for you: +https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! +We want to validate a few hypotheses using your feedback and also share our implementation considerations with you (to finalize this for production). + +Relevant Documentation + +Validation Module +https://docs.devexpress.com/eXpressAppFramework/113684/ + +Office Module +https://docs.devexpress.com/eXpressAppFramework/400003/ + +Dashboards Module +https://docs.devexpress.com/eXpressAppFramework/117449/xtensions \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/FileAttachments/Readme.txt b/DurnyklyYol.Blazor.Server/API/FileAttachments/Readme.txt new file mode 100644 index 0000000..ef4bb9d --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/FileAttachments/Readme.txt @@ -0,0 +1,16 @@ +Folder Description + +The "api/MediaFiles/" endpoints are intended to download BLOB data stored in databases (aka File Attachments). +You can download data from FileData, MediaDataObject, Image or byte array properties declared in your data models. +These additional benefits of our Web API Service ship as part of the DevExpress Universal Subscription (https://www.devexpress.com/buy/net/). + +YOUR FEEDBACK MATTERS +Please tell us how our Web API Service works for you: https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! + +Relevant Documentation + +Obtain BLOB Data from a Web API Controller Endpoint +https://docs.devexpress.com/eXpressAppFramework/404207/ + +File Attachments Module +https://docs.devexpress.com/eXpressAppFramework/112781/ \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/Localization/Readme.txt b/DurnyklyYol.Blazor.Server/API/Localization/Readme.txt new file mode 100644 index 0000000..0814269 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Localization/Readme.txt @@ -0,0 +1,19 @@ +Folder Description + +The "api/Localization/" endpoints are intended to obtain localized UI strings through HTTP requests to a Web API service. +You can use the localized UI strings in your custom application that uses the XAF Web API as a backend. +These additional benefits of our Web API Service ship as part of the DevExpress Universal Subscription (https://www.devexpress.com/buy/net/). + +YOUR FEEDBACK MATTERS +Please tell us how our Web API Service works for you: https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! + +Relevant Documentation + +Get Localization Strings from a Web API Controller Endpoint +https://docs.devexpress.com/eXpressAppFramework/403982/ + +Localization Module +https://docs.devexpress.com/eXpressAppFramework/113298/ + +Model Editor +https://docs.devexpress.com/eXpressAppFramework/112582 \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/Reports/Readme.txt b/DurnyklyYol.Blazor.Server/API/Reports/Readme.txt new file mode 100644 index 0000000..b8bf062 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Reports/Readme.txt @@ -0,0 +1,17 @@ +Folder Description + +The "api/Report/" endpoints are intended to download PDF, RTF, etc. from report templates (ReportDatav2) based on filtered data in databases. +Each ReportDatav2 container is stored in your database and links to an XtraReport, and report data sources, and data models that you designed in Visual Studio. +You can pass a report container identifier, data source criteria, export format, and other parameters to your endpoints. +These additional benefits of our Web API Service ship as part of the DevExpress Universal Subscription (https://www.devexpress.com/buy/net/). + +YOUR FEEDBACK MATTERS +Please tell us how our Web API Service works for you: https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! + +Relevant Documentation + +Obtain a Report from a Web API Controller Endpoint +https://docs.devexpress.com/eXpressAppFramework/404176/ + +Reports V2 Module +https://docs.devexpress.com/eXpressAppFramework/113591/ \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/Reports/ReportController.cs b/DurnyklyYol.Blazor.Server/API/Reports/ReportController.cs new file mode 100644 index 0000000..38d1ad8 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Reports/ReportController.cs @@ -0,0 +1,87 @@ +#nullable enable +using DevExpress.ExpressApp.ReportsV2.Services; +using DevExpress.ExpressApp.ReportsV2; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Xpo; +using DevExpress.Xpo.DB; +using DevExpress.XtraPrinting; +using DevExpress.XtraReports.UI; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +namespace DurnyklyYol.WebApi.Reports; + +[Authorize] +[Route("api/[controller]")] +// This is a WebApi Reports controller sample. +public class ReportController : ControllerBase { + private readonly IReportExportService service; + + public ReportController(IReportExportService reportExportService) { + service = reportExportService; + } + + private void ApplyParametersFromQuery(XtraReport report) { + foreach(var parameter in report.Parameters) { + var queryParam = Request.Query[parameter.Description]; + if(queryParam.Count > 0) { + parameter.Value = queryParam.First(); + } + } + } + private SortProperty[]? LoadSortPropertiesFromQuery() { + if(Request.Query.Keys.Contains("sortProperty")) { + var queryParam = Request.Query["sortProperty"]; + SortProperty[] result = new SortProperty[queryParam.Count]; + for(int i = 0; i < queryParam.Count; i++) { + string[] paramData = queryParam[i]!.Split(","); + result[i] = new SortProperty(paramData[0], (SortingDirection)Enum.Parse(typeof(SortingDirection), paramData[1])); + } + return result; + } + return null; + } + + private async Task GetReportContentAsync(XtraReport report, ExportTarget fileType) { + Stream ms = await service.ExportReportAsync(report, fileType); + HttpContext.Response.RegisterForDispose(ms); + return File(ms, service.GetContentType(fileType), $"{report.DisplayName}.{service.GetFileExtension(fileType)}"); + } + + [HttpGet("DownloadByKey({key})")] + [SwaggerOperation("Gets the contents of a report specified by its key in the specified file format filtered based on the specified condition.", "For more information, refer to the following article: Obtain a Report from a Web API Controller Endpoint.")] + public async Task DownloadByKey( + [SwaggerParameter("A primary key value that uniquely identifies a report.
Example: '83978d7f-82b7-4380-979a-08db4587a66b'")] + string key, + [FromQuery, SwaggerParameter("The file type in which to download the report.")] + ExportTarget fileType = ExportTarget.Pdf, + [FromQuery, SwaggerParameter("A condition used to filter the report's data.
Example: \"[FirstName] = 'Aaron'\"")] + string? criteria = null) { + using var report = service.LoadReport(key); + ApplyParametersFromQuery(report); + SortProperty[]? sortProperties = LoadSortPropertiesFromQuery(); + service.SetupReport(report, criteria, sortProperties); + return await GetReportContentAsync(report, fileType); + } + + [HttpGet("DownloadByName({displayName})")] + [SwaggerOperation("Gets the contents of a report specified by its display name in the specified file format filtered based on the specified condition.", "For more information, refer to the following article: Obtain a Report from a Web API Controller Endpoint.")] + public async Task DownloadByName( + [SwaggerParameter("The display name of a report to download.
Example: 'Employee List Report'")] + string displayName, + [FromQuery, SwaggerParameter("The file type in which to download the report.")] + ExportTarget fileType = ExportTarget.Pdf, + [FromQuery, SwaggerParameter("A condition used to filter the report's data.
Example: \"[FirstName] = 'Aaron'\"")] + string? criteria = null) { + if(!string.IsNullOrEmpty(displayName)) { + using var report = service.LoadReport(data => data.DisplayName == displayName); + ApplyParametersFromQuery(report); + SortProperty[]? sortProperties = LoadSortPropertiesFromQuery(); + service.SetupReport(report, criteria, sortProperties); + return await GetReportContentAsync(report, fileType); + } + return NotFound(); + } +} +#nullable restore \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/API/Security/AuthenticationController.cs b/DurnyklyYol.Blazor.Server/API/Security/AuthenticationController.cs new file mode 100644 index 0000000..5fd2369 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Security/AuthenticationController.cs @@ -0,0 +1,32 @@ +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Security.Authentication; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +namespace DurnyklyYol.WebApi.JWT; + +[ApiController] +[Route("api/[controller]")] +// This is a JWT authentication service sample. +public class AuthenticationController : ControllerBase { + readonly IAuthenticationTokenProvider tokenProvider; + public AuthenticationController(IAuthenticationTokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + [HttpPost("Authenticate")] + [SwaggerOperation("Checks if the user with the specified logon parameters exists in the database. If it does, authenticates this user.", "Refer to the following help topic for more information on authentication methods in the XAF Security System: Authentication.")] + public IActionResult Authenticate( + [FromBody] + [SwaggerRequestBody(@"For example:
{ ""userName"": ""Admin"", ""password"": """" }")] + AuthenticationStandardLogonParameters logonParameters + ) { + try { + return Ok(tokenProvider.Authenticate(logonParameters)); + } + catch(AuthenticationException ex) { + return Unauthorized(ex.GetJson()); + } + } +} diff --git a/DurnyklyYol.Blazor.Server/API/Security/JwtTokenProviderService.cs b/DurnyklyYol.Blazor.Server/API/Security/JwtTokenProviderService.cs new file mode 100644 index 0000000..4a75de8 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Security/JwtTokenProviderService.cs @@ -0,0 +1,37 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Runtime.ExceptionServices; +using System.Security.Claims; +using System.Text; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using Microsoft.IdentityModel.Tokens; + +namespace DurnyklyYol.WebApi.JWT; + +public class JwtTokenProviderService : IAuthenticationTokenProvider { + readonly SignInManager signInManager; + readonly IConfiguration configuration; + public JwtTokenProviderService(SignInManager signInManager, IConfiguration configuration) { + this.signInManager = signInManager; + this.configuration = configuration; + } + public string Authenticate(object logonParameters) { + var result = signInManager.AuthenticateByLogonParameters(logonParameters); + if(result.Succeeded) { + var issuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Authentication:Jwt:IssuerSigningKey"])); + var token = new JwtSecurityToken( + //issuer: configuration["Authentication:Jwt:Issuer"], + //audience: configuration["Authentication:Jwt:Audience"], + claims: result.Principal.Claims, + expires: DateTime.Now.AddDays(2), + signingCredentials: new SigningCredentials(issuerSigningKey, SecurityAlgorithms.HmacSha256) + ); + return new JwtSecurityTokenHandler().WriteToken(token); + } + if(result.Error is IUserFriendlyException) { + ExceptionDispatchInfo.Throw(result.Error); + } + throw new AuthenticationException("Internal server error"); + } +} diff --git a/DurnyklyYol.Blazor.Server/API/Security/Readme.txt b/DurnyklyYol.Blazor.Server/API/Security/Readme.txt new file mode 100644 index 0000000..0a449d3 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/API/Security/Readme.txt @@ -0,0 +1,21 @@ +Folder Description + +The "api/Authentication/" endpoint is intended to authenticate users based on logon parameters (a name and password). +The generated JWT authentication tokens are used to access API Controllers securely. + +YOUR FEEDBACK MATTERS +Please tell us how our Web API Service works for you: https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! + +Relevant Documentation + +Use the Swagger UI to Test the JWT Authentication +https://docs.devexpress.com/eXpressAppFramework/403504/ + +Access Security System in a Web API Controller Endpoint +https://docs.devexpress.com/eXpressAppFramework/403861/ + +Overview of ASP.NET Core authentication +https://learn.microsoft.com/en-us/aspnet/core/security/authentication/ + +Security System Module +https://docs.devexpress.com/eXpressAppFramework/113591/ \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/App.razor b/DurnyklyYol.Blazor.Server/App.razor new file mode 100644 index 0000000..3b8af49 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/App.razor @@ -0,0 +1,11 @@ + + + + + + + Not found +

Sorry, there's nothing at this address.

+
+
+
diff --git a/DurnyklyYol.Blazor.Server/BlazorApplication.cs b/DurnyklyYol.Blazor.Server/BlazorApplication.cs new file mode 100644 index 0000000..2e581e8 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/BlazorApplication.cs @@ -0,0 +1,49 @@ +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.ApplicationBuilder; +using DevExpress.ExpressApp.Blazor; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Security.ClientServer; +using DevExpress.ExpressApp.SystemModule; +using DevExpress.ExpressApp.Xpo; +using DurnyklyYol.Blazor.Server.Services; + +namespace DurnyklyYol.Blazor.Server; + +public class DurnyklyYolBlazorApplication : BlazorApplication { + public DurnyklyYolBlazorApplication() { + ApplicationName = "DurnyklyYol"; + CheckCompatibilityType = DevExpress.ExpressApp.CheckCompatibilityType.DatabaseSchema; + DatabaseVersionMismatch += DurnyklyYolBlazorApplication_DatabaseVersionMismatch; + } + protected override void OnSetupStarted() { + base.OnSetupStarted(); +#if DEBUG + if(System.Diagnostics.Debugger.IsAttached && CheckCompatibilityType == CheckCompatibilityType.DatabaseSchema) { + DatabaseUpdateMode = DatabaseUpdateMode.UpdateDatabaseAlways; + } +#endif + } + private void DurnyklyYolBlazorApplication_DatabaseVersionMismatch(object sender, DatabaseVersionMismatchEventArgs e) { +#if EASYTEST + e.Updater.Update(); + e.Handled = true; +#else + if(System.Diagnostics.Debugger.IsAttached) { + e.Updater.Update(); + e.Handled = true; + } + else { + string message = "The application cannot connect to the specified database, " + + "because the database doesn't exist, its version is older " + + "than that of the application or its schema does not match " + + "the ORM data model structure. To avoid this error, use one " + + "of the solutions from the https://www.devexpress.com/kb=T367835 KB Article."; + + if(e.CompatibilityError != null && e.CompatibilityError.Exception != null) { + message += "\r\n\r\nInner exception: " + e.CompatibilityError.Exception.Message; + } + throw new InvalidOperationException(message); + } +#endif + } +} diff --git a/DurnyklyYol.Blazor.Server/BlazorModule.cs b/DurnyklyYol.Blazor.Server/BlazorModule.cs new file mode 100644 index 0000000..4b0551f --- /dev/null +++ b/DurnyklyYol.Blazor.Server/BlazorModule.cs @@ -0,0 +1,38 @@ +using System.ComponentModel; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Actions; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Model.Core; +using DevExpress.ExpressApp.Model.DomainLogics; +using DevExpress.ExpressApp.Model.NodeGenerators; +using DevExpress.Persistent.BaseImpl; + +namespace DurnyklyYol.Blazor.Server; + +[ToolboxItemFilter("Xaf.Platform.Blazor")] +// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase. +public sealed class DurnyklyYolBlazorModule : ModuleBase { + //private void Application_CreateCustomModelDifferenceStore(object sender, CreateCustomModelDifferenceStoreEventArgs e) { + // e.Store = new ModelDifferenceDbStore((XafApplication)sender, typeof(ModelDifference), true, "Blazor"); + // e.Handled = true; + //} + private void Application_CreateCustomUserModelDifferenceStore(object sender, CreateCustomModelDifferenceStoreEventArgs e) { + e.Store = new ModelDifferenceDbStore((XafApplication)sender, typeof(ModelDifference), false, "Blazor"); + e.Handled = true; + } + public DurnyklyYolBlazorModule() { + } + public override IEnumerable GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) { + return ModuleUpdater.EmptyModuleUpdaters; + } + public override void Setup(XafApplication application) { + base.Setup(application); + // Uncomment this code to store the shared model differences (administrator settings in Model.XAFML) in the database. + // For more information, refer to the following topic: https://docs.devexpress.com/eXpressAppFramework/113698/ + //application.CreateCustomModelDifferenceStore += Application_CreateCustomModelDifferenceStore; + application.CreateCustomUserModelDifferenceStore += Application_CreateCustomUserModelDifferenceStore; + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/CargoController.cs b/DurnyklyYol.Blazor.Server/Controllers/CargoController.cs new file mode 100644 index 0000000..f887494 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/CargoController.cs @@ -0,0 +1,86 @@ +using DevExpress.ExpressApp.Core; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.WebApi.Services; +using DurnyklyYol.Blazor.Server.DTO; +using DurnyklyYol.Module.BusinessObjects; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DurnyklyYol.Blazor.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CargoController : ControllerBase + { + private readonly INonSecuredObjectSpaceFactory _nonSecuredObjectSpaceFactory; + public CargoController(INonSecuredObjectSpaceFactory nonSecuredObjectSpaceFactory) => _nonSecuredObjectSpaceFactory = nonSecuredObjectSpaceFactory; + + + [HttpGet("Route/{cargoId}")] + public IActionResult GetRoute(Guid cargoId) + { + try + { + using var objectSpace = _nonSecuredObjectSpaceFactory.CreateNonSecuredObjectSpace(typeof(Cargo)); + var cargo = objectSpace.GetObjectByKey(cargoId); + + if (cargo == null) + { + return NotFound(); + } + + var route = objectSpace.GetObjectsQuery() + .Where(wh => wh.Oid == cargo.Route.Oid) + .Select(sl => new + { + StartName = sl.StartPoint.Name, + Startpoint = sl.StartPoint.Point, + DestName = sl.DestinationPoint.Name, + Destpoint = sl.DestinationPoint.Point, + Points = sl.Points.OrderBy(ob => ob.Order).Select(p => p.Point) + }) + .FirstOrDefault(); + + if (route == null) + { + return NotFound(); + } + + var points = route.Points.Select(p => new PointDto + { + Name = p.Name, + Lat = p.Latitude, + Long = p.Longitude, + IsCurrent = cargo.CurrentPoint.Oid == p.Oid, + + }).ToList(); + + points.Insert(0, new PointDto + { + Name = route.StartName, + Lat = route.Startpoint?.Latitude ?? 0, + Long = route.Startpoint?.Longitude ?? 0, + IsCurrent = cargo.CurrentPoint.Oid == route.Startpoint?.Oid, + DateAt = cargo.StartedAt + }); + + points.Add(new PointDto + { + Name = route.DestName, + Lat = route.Destpoint?.Latitude ?? 0, + Long = route.Destpoint?.Longitude ?? 0, + IsCurrent = cargo.CurrentPoint.Oid == route.Destpoint?.Oid, + DateAt = cargo.ArrivedAt + }); + + + return Ok(points); + } + catch (Exception ex) { + return StatusCode(500, ex.Message); + } + + } + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/ClientController.cs b/DurnyklyYol.Blazor.Server/Controllers/ClientController.cs new file mode 100644 index 0000000..d66d969 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/ClientController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using DevExpress.ExpressApp.Security; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.SystemModule; +using DevExpress.Persistent.Base; +using DevExpress.ExpressApp.WebApi; +using System.Linq; +using DevExpress.ExpressApp.Core; +using DurnyklyYol.Module.BusinessObjects; +using Microsoft.AspNetCore.Authorization; +using DevExpress.ExpressApp.WebApi.Services; + +namespace DurnyklyYol.Blazor.Server.Controllers +{ + [ApiController] + [Route("api/[controller]")] + [Authorize] + public class ClientController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private readonly IDataService dataService; + private readonly ISecurityProvider securityProvider; + + public ClientController(IDataService dataService, ISecurityProvider securityProvider) + { + this.dataService = dataService; + this.securityProvider = securityProvider; + } + + [HttpGet(nameof(GetClient))] + [Authorize] + public IActionResult GetClient() + { + var objectSpace = dataService.GetObjectSpace(); + var userID = (Guid)securityProvider.GetSecurity().UserId; + var clients = objectSpace.GetObjectsQuery() + .Select(sl => new { Fulname = sl.FullName, Oid = sl.Oid, PhoneNo = sl.Telefon}) + .First(cl => cl.Oid == userID); + + + return Ok(clients); + } + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/GoodsController.cs b/DurnyklyYol.Blazor.Server/Controllers/GoodsController.cs new file mode 100644 index 0000000..515ac2d --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/GoodsController.cs @@ -0,0 +1,81 @@ +using DevExpress.CodeParser; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.WebApi.Services; +using DurnyklyYol.Blazor.Server.DTO; +using DurnyklyYol.Module.BusinessObjects; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace DurnyklyYol.Blazor.Server.Controllers +{ + [ApiController] + [Route("api/[controller]")] + [Authorize] + public class GoodsController : ControllerBase + { + private readonly IDataService dataService; + private readonly ISecurityProvider securityProvider; + + public GoodsController(IDataService dataService, ISecurityProvider securityProvider) + { + this.dataService = dataService; + this.securityProvider = securityProvider; + } + + [Authorize] + [HttpGet] + public IActionResult Get([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10, [FromQuery] GoodsState? state = null) + { + var objectSpace = dataService.GetObjectSpace(); + var userID = (Guid)securityProvider.GetSecurity().UserId; + var goodsQuery = objectSpace.GetObjectsQuery() + .Where(cl => cl.Client.Oid == userID) + .Select(sl => new GoodsDto { + No = sl.No, + Oid = sl.Oid, + Name = sl.Name, + PlacesCount = sl.PlaceCount, + Volume = sl.Volume, + ShopNo = sl.ShopNo, + Carrier = sl.Cargo.Carrier.Number, + From = sl.Cargo.StartsFrom, + To = sl.Cargo.DestinationTo, + ClientId = sl.Client.Oid, + DepartedAt = sl.Cargo.StartedAt, + Depth = sl.Depth, + Width = sl.Width, + Height = sl.Height, + CargoId = sl.Cargo.Oid, + CargoState = sl.Cargo.State, + State = sl.State, + Price = sl.Price, + Image1 = sl.Image1 != null ? string.Format("/FileData/{0}-{1}", sl.Image1.Oid, sl.Image1.FileName) : "", + Image2 = sl.Image2 != null ? string.Format("/FileData/{0}-{1}", sl.Image2.Oid, sl.Image2.FileName) : "", + Image3 = sl.Image3 != null ? string.Format("/FileData/{0}-{1}", sl.Image3.Oid, sl.Image3.FileName) : "", + }); + + if (state != null) { + goodsQuery.Where(wh => wh.State == state); + } + + var totalRecords = goodsQuery.Count(); + + var goods = goodsQuery + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToList(); + + var response = new + { + TotalRecords = totalRecords, + PageNumber = pageNumber, + PageSize = pageSize, + Data = goods + }; + + return Ok(response); + } + + + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.Designer.cs b/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.Designer.cs new file mode 100644 index 0000000..98ad44e --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.Designer.cs @@ -0,0 +1,36 @@ +namespace DurnyklyYol.Blazor.Server.Controllers +{ + partial class GoodsImageUploadController + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.cs b/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.cs new file mode 100644 index 0000000..be00646 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/GoodsImageUploadController.cs @@ -0,0 +1,57 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Actions; +using DevExpress.ExpressApp.Blazor.Components.Models; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Layout; +using DevExpress.ExpressApp.Model.NodeGenerators; +using DevExpress.ExpressApp.SystemModule; +using DevExpress.ExpressApp.Templates; +using DevExpress.ExpressApp.Utils; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.Validation; +using DurnyklyYol.Module.BusinessObjects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DevExpress.ExpressApp.Blazor.Editors.Adapters; +using DevExpress.ExpressApp.Blazor.SystemModule; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Xpo; +using Microsoft.AspNetCore.Components; + +namespace DurnyklyYol.Blazor.Server.Controllers +{ + // For more typical usage scenarios, be sure to check out https://documentation.devexpress.com/eXpressAppFramework/clsDevExpressExpressAppViewControllertopic.aspx. + public partial class GoodsImageUploadController : ViewController + { + // Use CodeRush to create Controllers and Actions with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/403133/ + public GoodsImageUploadController() + { + InitializeComponent(); + TargetObjectType = typeof(Goods); + // Target required Views (via the TargetXXX properties) and create their Actions. + } + protected override void OnActivated() + { + base.OnActivated(); + // Perform various tasks depending on the target View. + if (View is DetailView detailView && detailView.CurrentObject is Goods goods) + { + + } + } + protected override void OnViewControlsCreated() + { + base.OnViewControlsCreated(); + // Access and customize the target View control. + } + protected override void OnDeactivated() + { + // Unsubscribe from previously subscribed events and release other references and resources. + base.OnDeactivated(); + } + } +} diff --git a/DurnyklyYol.Blazor.Server/Controllers/ReadMe.txt b/DurnyklyYol.Blazor.Server/Controllers/ReadMe.txt new file mode 100644 index 0000000..42f3017 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Controllers/ReadMe.txt @@ -0,0 +1,25 @@ +Folder Description + +The "Controllers" project folder is intended for storing Blazor-specific Controller classes +that can change the default XAF application flow and add new features. + + +Relevant Documentation + +Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/112623 + +Implement Custom Controllers +https://docs.devexpress.com/eXpressAppFramework/112621 + +Define the Scope of Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/113103 + +Ways to Show a View +https://docs.devexpress.com/eXpressAppFramework/112803 + +Ways to Implement Business Logic +https://docs.devexpress.com/eXpressAppFramework/113710 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Blazor.Server/DTO/CargoDto.cs b/DurnyklyYol.Blazor.Server/DTO/CargoDto.cs new file mode 100644 index 0000000..82cb396 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/DTO/CargoDto.cs @@ -0,0 +1,11 @@ +namespace DurnyklyYol.Blazor.Server.DTO +{ + public class CargoDto + { + public string No { get; set; } + public PointDto Start { get; set; } + public PointDto End { get; set; } + public IEnumerable Points { get; set; } + + } +} diff --git a/DurnyklyYol.Blazor.Server/DTO/GoodsListDto.cs b/DurnyklyYol.Blazor.Server/DTO/GoodsListDto.cs new file mode 100644 index 0000000..1e6624b --- /dev/null +++ b/DurnyklyYol.Blazor.Server/DTO/GoodsListDto.cs @@ -0,0 +1,31 @@ +using DurnyklyYol.Module.BusinessObjects; + +namespace DurnyklyYol.Blazor.Server.DTO +{ + public class GoodsDto + { + public Guid Oid { get; set; } + public Guid ClientId { get; set; } + public Guid CargoId { get; set; } + public GoodsState State { get; set; } + public CargoState CargoState { get; set; } + public string No { get; set; } + public string Name { get; set; } + public string ShopNo { get; set; } + public double Volume { get; set; } + public uint PlacesCount { get; set; } + public string Carrier { get; set; } + public string From { get; set; } + public string To { get; set; } + public DateTime? DepartedAt { get; set; } + public uint Depth { get; set; } + public uint Width { get; set; } + public uint Height { get; set; } + public decimal Price { get; set; } + + public string Image1 { get; set; } + public string Image2 { get; set; } + public string Image3 { get; set; } + + } +} diff --git a/DurnyklyYol.Blazor.Server/DTO/PointDto.cs b/DurnyklyYol.Blazor.Server/DTO/PointDto.cs new file mode 100644 index 0000000..7933702 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/DTO/PointDto.cs @@ -0,0 +1,12 @@ +namespace DurnyklyYol.Blazor.Server.DTO +{ + public class PointDto + { + public string Name { get; set; } + public double Lat { get; set; } + public double Long { get; set; } + public bool IsCurrent { get; set; } + public DateTime? DateAt { get; set; } + + } +} diff --git a/DurnyklyYol.Blazor.Server/DurnyklyYol.Blazor.Server.csproj b/DurnyklyYol.Blazor.Server/DurnyklyYol.Blazor.Server.csproj new file mode 100644 index 0000000..963abc3 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/DurnyklyYol.Blazor.Server.csproj @@ -0,0 +1,56 @@ + + + net8.0 + false + false + 1.0.* + 1.0.0.0 + Debug;Release;EasyTest + enable + + + + + + + + + + + Always + + + Model.xafml + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="GoodsImageUpload.razor" /> + + + + + diff --git a/DurnyklyYol.Blazor.Server/Editors/GoodsImageUploadPropertyEditor.cs b/DurnyklyYol.Blazor.Server/Editors/GoodsImageUploadPropertyEditor.cs new file mode 100644 index 0000000..5a4e7d0 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Editors/GoodsImageUploadPropertyEditor.cs @@ -0,0 +1,17 @@ +using DevExpress.ExpressApp.Blazor.Editors; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Model; +using Microsoft.AspNetCore.Components; + +namespace DurnyklyYol.Blazor.Server.Editors +{ + [PropertyEditor(typeof(string), "GoodsImageUploadPropertyEditor", false)] + public class GoodsImageUploadPropertyEditor: BlazorPropertyEditorBase + { + public GoodsImageUploadPropertyEditor(Type objectType, IModelMemberViewItem model) + : base(objectType, model) + { + } + + } +} diff --git a/DurnyklyYol.Blazor.Server/Editors/ReadMe.txt b/DurnyklyYol.Blazor.Server/Editors/ReadMe.txt new file mode 100644 index 0000000..827fd95 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Editors/ReadMe.txt @@ -0,0 +1,25 @@ +Folder Description + +This project folder is intended for storing custom Blazor List Editors, +Property Editors and View Items. + + +Relevant Documentation + +Using a Custom Control that is not Integrated by Default +https://docs.devexpress.com/eXpressAppFramework/113610 + +Ways to Access UI Elements and Their Controls +https://docs.devexpress.com/eXpressAppFramework/120092 + +Views +https://docs.devexpress.com/eXpressAppFramework/112611 + +List Editors +https://docs.devexpress.com/eXpressAppFramework/113189 + +View Items +https://docs.devexpress.com/eXpressAppFramework/112612 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Blazor.Server/GoodsImageUpload.razor b/DurnyklyYol.Blazor.Server/GoodsImageUpload.razor new file mode 100644 index 0000000..1dd0bbf --- /dev/null +++ b/DurnyklyYol.Blazor.Server/GoodsImageUpload.razor @@ -0,0 +1,40 @@ + +@page "/upload-goods-images" + +@inject IObjectSpace ObjectSpace +@inject IWebHostEnvironment Environment + + + +@code { + [Parameter] + public Goods Goods { get; set; } + + private async Task HandleFileSelected(InputFileChangeEventArgs e) + { + var files = e.GetMultipleFiles(); + + foreach (var file in files) + { + var imageName = $"{Guid.NewGuid()}_{file.Name}"; + var path = Path.Combine(Environment.WebRootPath, "uploads", imageName); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var fileStream = new FileStream(path, FileMode.Create)) + { + await file.OpenReadStream().CopyToAsync(fileStream); + } + + var imageUrl = $"uploads/{imageName}"; + + var goodsImage = ObjectSpace.CreateObject(); + goodsImage.Goods = Goods; + goodsImage.ImageUrl = imageUrl; + + Goods.Images.Add(goodsImage); + } + + ObjectSpace.CommitChanges(); + } +} diff --git a/DurnyklyYol.Blazor.Server/Images/ReadMe.txt b/DurnyklyYol.Blazor.Server/Images/ReadMe.txt new file mode 100644 index 0000000..230befc --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Images/ReadMe.txt @@ -0,0 +1,12 @@ +Folder Description + +The "Images" project folder is intended for storing custom image files. + + +Relevant Documentation + +Add and Override Images +https://docs.devexpress.com/eXpressAppFramework/112792 + +Assign a Custom Image +https://docs.devexpress.com/eXpressAppFramework/112744 diff --git a/DurnyklyYol.Blazor.Server/Model.xafml b/DurnyklyYol.Blazor.Server/Model.xafml new file mode 100644 index 0000000..60b3179 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Model.xafml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/Model_tk-TM.xafml b/DurnyklyYol.Blazor.Server/Model_tk-TM.xafml new file mode 100644 index 0000000..e316070 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Model_tk-TM.xafml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/OData/MyODataQueryValidator.cs b/DurnyklyYol.Blazor.Server/OData/MyODataQueryValidator.cs new file mode 100644 index 0000000..14d9644 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/OData/MyODataQueryValidator.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.OData.Query.Validator; +using Microsoft.AspNetCore.OData.Query; + +namespace DurnyklyYol.Blazor.Server.OData +{ + public class MyODataQueryValidator : ODataQueryValidator + { + public override void Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) + { + validationSettings.MaxExpansionDepth = 6; + base.Validate(options, validationSettings); + } + } +} diff --git a/DurnyklyYol.Blazor.Server/Pages/_Host.cshtml b/DurnyklyYol.Blazor.Server/Pages/_Host.cshtml new file mode 100644 index 0000000..9f3e694 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Pages/_Host.cshtml @@ -0,0 +1,57 @@ +@page "/" +@namespace DurnyklyYol.Blazor.Server +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using DevExpress.ExpressApp.Blazor.Components + + + + + + + + + + DurnyklyYol + + + + + @{ + string userAgent = Request.Headers["User-Agent"]; + bool isIE = userAgent.Contains("MSIE") || userAgent.Contains("Trident"); + } + @if(isIE) { + +
+
+ +
+
Internet Explorer is not supported.
+

DurnyklyYol cannot be loaded in Internet Explorer.
Please use a different browser.

+
+
+
+ } + else { + + + + + // Uncomment this link to enable CSS isolation. For more information, refer to the following topic: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation + // + + + + + + + + +
+ +
+ + + } + + diff --git a/DurnyklyYol.Blazor.Server/Program.cs b/DurnyklyYol.Blazor.Server/Program.cs new file mode 100644 index 0000000..3317bd5 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Program.cs @@ -0,0 +1,52 @@ +using System.Reflection; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Blazor.DesignTime; +using DevExpress.ExpressApp.Blazor.Services; +using DevExpress.ExpressApp.Design; +using DevExpress.ExpressApp.Utils; + +namespace DurnyklyYol.Blazor.Server; + +public class Program : IDesignTimeApplicationFactory { + private static bool ContainsArgument(string[] args, string argument) { + return args.Any(arg => arg.TrimStart('/').TrimStart('-').ToLower() == argument.ToLower()); + } + public static int Main(string[] args) { + if(ContainsArgument(args, "help") || ContainsArgument(args, "h")) { + Console.WriteLine("Updates the database when its version does not match the application's version."); + Console.WriteLine(); + Console.WriteLine($" {Assembly.GetExecutingAssembly().GetName().Name}.exe --updateDatabase [--forceUpdate --silent]"); + Console.WriteLine(); + Console.WriteLine("--forceUpdate - Marks that the database must be updated whether its version matches the application's version or not."); + Console.WriteLine("--silent - Marks that database update proceeds automatically and does not require any interaction with the user."); + Console.WriteLine(); + Console.WriteLine($"Exit codes: 0 - {DBUpdaterStatus.UpdateCompleted}"); + Console.WriteLine($" 1 - {DBUpdaterStatus.UpdateError}"); + Console.WriteLine($" 2 - {DBUpdaterStatus.UpdateNotNeeded}"); + } + else { + DevExpress.ExpressApp.FrameworkSettings.DefaultSettingsCompatibilityMode = DevExpress.ExpressApp.FrameworkSettingsCompatibilityMode.Latest; + DevExpress.ExpressApp.Security.SecurityStrategy.AutoAssociationReferencePropertyMode = DevExpress.ExpressApp.Security.ReferenceWithoutAssociationPermissionsMode.AllMembers; + IHost host = CreateHostBuilder(args).Build(); + if(ContainsArgument(args, "updateDatabase")) { + using(var serviceScope = host.Services.CreateScope()) { + return serviceScope.ServiceProvider.GetRequiredService().Update(ContainsArgument(args, "forceUpdate"), ContainsArgument(args, "silent")); + } + } + else { + host.Run(); + } + } + return 0; + } + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { + webBuilder.UseStartup(); + //webBuilder.UseUrls("http://0.0.0.0:5555"); + }); + XafApplication IDesignTimeApplicationFactory.Create() { + IHostBuilder hostBuilder = CreateHostBuilder(Array.Empty()); + return DesignTimeApplicationFactoryHelper.Create(hostBuilder); + } +} diff --git a/DurnyklyYol.Blazor.Server/Properties/launchSettings.json b/DurnyklyYol.Blazor.Server/Properties/launchSettings.json new file mode 100644 index 0000000..1e08b1e --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:65201", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DurnyklyYol.Blazor.Server": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://192.168.99.64:5001;http://192.168.99.64:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/ReadMe.txt b/DurnyklyYol.Blazor.Server/ReadMe.txt new file mode 100644 index 0000000..a229505 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/ReadMe.txt @@ -0,0 +1,40 @@ +Project Description + +This project implements an ASP.NET Core Blazor Server application (https://docs.microsoft.com/en-us/aspnet/core/blazor/). +The root project folder contains the BlazorApplication.cs file with the class that inherits +BlazorApplication. This class allows you to view and customize application components: referenced modules, +security settings, data connection. Additionally, the root folder contains +Application Model difference files (XAFML files) that keep settings +specific to the current application. Difference files can be customized in code +or in the Model Editor. +The appsettings.json file contains database connection, logging, and theme settings (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration). + + +Relevant Documentation + +Application Solution Components +https://docs.devexpress.com/eXpressAppFramework/112569 + +Debugging, Testing and Error Handling +https://docs.devexpress.com/eXpressAppFramework/112572 + +XafApplication Class +https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.XafApplication + +Application Model (UI Settings Storage) +https://docs.devexpress.com/eXpressAppFramework/112579 + +Model Editor +https://docs.devexpress.com/eXpressAppFramework/112582 + +ASP.NET Core Blazor UI +https://docs.devexpress.com/eXpressAppFramework/401675/overview/supported-ui-platforms#aspnet-core-blazor-ui + +Frequently Asked Questions - Blazor UI (FAQ) +https://docs.devexpress.com/eXpressAppFramework/403277/support-qa-troubleshooting/frequently-asked-questions-blazor-faq + +Backend WebApi Service +https://docs.devexpress.com/eXpressAppFramework/403394/backend-web-api-service + +XAF Community Extensions +https://www.devexpress.com/products/net/application_framework/#extensions \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/Services/CircuitHandlerProxy.cs b/DurnyklyYol.Blazor.Server/Services/CircuitHandlerProxy.cs new file mode 100644 index 0000000..0a0c526 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Services/CircuitHandlerProxy.cs @@ -0,0 +1,23 @@ +using DevExpress.ExpressApp.Blazor.Services; +using Microsoft.AspNetCore.Components.Server.Circuits; + +namespace DurnyklyYol.Blazor.Server.Services; + +internal class CircuitHandlerProxy : CircuitHandler { + private readonly IScopedCircuitHandler scopedCircuitHandler; + public CircuitHandlerProxy(IScopedCircuitHandler scopedCircuitHandler) { + this.scopedCircuitHandler = scopedCircuitHandler; + } + public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) { + return scopedCircuitHandler.OnCircuitOpenedAsync(cancellationToken); + } + public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken) { + return scopedCircuitHandler.OnConnectionUpAsync(cancellationToken); + } + public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken) { + return scopedCircuitHandler.OnCircuitClosedAsync(cancellationToken); + } + public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken) { + return scopedCircuitHandler.OnConnectionDownAsync(cancellationToken); + } +} diff --git a/DurnyklyYol.Blazor.Server/Services/ProxyHubConnectionHandler.cs b/DurnyklyYol.Blazor.Server/Services/ProxyHubConnectionHandler.cs new file mode 100644 index 0000000..1bb6f52 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Services/ProxyHubConnectionHandler.cs @@ -0,0 +1,27 @@ +using DevExpress.ExpressApp.Blazor.Services; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Options; + +namespace DurnyklyYol.Blazor.Server.Services; + +internal class ProxyHubConnectionHandler : HubConnectionHandler where THub : Hub { + private readonly IValueManagerStorageContainerInitializer storageContainerInitializer; + public ProxyHubConnectionHandler( + HubLifetimeManager lifetimeManager, + IHubProtocolResolver protocolResolver, + IOptions globalHubOptions, + IOptions> hubOptions, + ILoggerFactory loggerFactory, + IUserIdProvider userIdProvider, + IServiceScopeFactory serviceScopeFactory, + IValueManagerStorageContainerInitializer storageContainerAccessor) + : base(lifetimeManager, protocolResolver, globalHubOptions, hubOptions, loggerFactory, userIdProvider, serviceScopeFactory) { + this.storageContainerInitializer = storageContainerAccessor; + } + + public override Task OnConnectedAsync(ConnectionContext connection) { + storageContainerInitializer.Initialize(); + return base.OnConnectedAsync(connection); + } +} diff --git a/DurnyklyYol.Blazor.Server/Startup.cs b/DurnyklyYol.Blazor.Server/Startup.cs new file mode 100644 index 0000000..f245178 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/Startup.cs @@ -0,0 +1,210 @@ +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.ApplicationBuilder; +using DevExpress.ExpressApp.Blazor.ApplicationBuilder; +using DevExpress.ExpressApp.Blazor.Services; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Components.Server.Circuits; +using DurnyklyYol.Blazor.Server.Services; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.OData; +using DevExpress.ExpressApp.WebApi.Services; +using DurnyklyYol.WebApi.JWT; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using DurnyklyYol.Blazor.Server.OData; +using Microsoft.AspNetCore.OData.Query.Validator; + +namespace DurnyklyYol.Blazor.Server; + +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. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) { + services.AddSingleton(typeof(Microsoft.AspNetCore.SignalR.HubConnectionHandler<>), typeof(ProxyHubConnectionHandler<>)); + + services.AddRazorPages(); + services.AddServerSideBlazor(); + services.AddHttpContextAccessor(); + services.AddScoped(); + services.AddScoped(); + services.AddXaf(Configuration, builder => { + builder.UseApplication(); + + builder.AddXafWebApi(webApiBuilder => { + webApiBuilder.AddXpoServices(); + + webApiBuilder.ConfigureOptions(options => { + // Make your business objects available in the Web API and generate the GET, POST, PUT, and DELETE HTTP methods for it. + // options.BusinessObject(); + options.BusinessObject(); + }); + }); + + builder.Modules + .AddConditionalAppearance() + .AddFileAttachments() + .AddReports(options => { + options.EnableInplaceReports = true; + options.ReportDataType = typeof(DevExpress.Persistent.BaseImpl.ReportDataV2); + options.ReportStoreMode = DevExpress.ExpressApp.ReportsV2.ReportStoreModes.XML; + }) + .AddValidation(options => { + options.AllowValidationDetailsAccess = false; + }) + .AddViewVariants() + .Add() + .Add(); + builder.ObjectSpaceProviders + .AddSecuredXpo((serviceProvider, options) => { + string connectionString = null; + if(Configuration.GetConnectionString("ConnectionString") != null) { + connectionString = Configuration.GetConnectionString("ConnectionString"); + } +#if EASYTEST + if(Configuration.GetConnectionString("EasyTestConnectionString") != null) { + connectionString = Configuration.GetConnectionString("EasyTestConnectionString"); + } +#endif + ArgumentNullException.ThrowIfNull(connectionString); + options.ConnectionString = connectionString; + options.ThreadSafe = true; + options.UseSharedDataStoreProvider = true; + }) + .AddNonPersistent(); + builder.Security + .UseIntegratedMode(options => { + options.Lockout.Enabled = true; + + options.RoleType = typeof(PermissionPolicyRole); + // ApplicationUser descends from PermissionPolicyUser and supports the OAuth authentication. For more information, refer to the following topic: https://docs.devexpress.com/eXpressAppFramework/402197 + // If your application uses PermissionPolicyUser or a custom user type, set the UserType property as follows: + options.UserType = typeof(DurnyklyYol.Module.BusinessObjects.ApplicationUser); + // ApplicationUserLoginInfo is only necessary for applications that use the ApplicationUser user type. + // If you use PermissionPolicyUser or a custom user type, comment out the following line: + options.UserLoginInfoType = typeof(DurnyklyYol.Module.BusinessObjects.ApplicationUserLoginInfo); + options.UseXpoPermissionsCaching(); + options.Events.OnSecurityStrategyCreated += securityStrategy => { + // Use the 'PermissionsReloadMode.NoCache' option to load the most recent permissions from the database once + // for every Session instance when secured data is accessed through this instance for the first time. + // Use the 'PermissionsReloadMode.CacheOnFirstAccess' option to reduce the number of database queries. + // In this case, permission requests are loaded and cached when secured data is accessed for the first time + // and used until the current user logs out. + // See the following article for more details: https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Security.SecurityStrategy.PermissionsReloadMode. + ((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.NoCache; + }; + }) + .AddPasswordAuthentication(options => { + options.IsSupportChangePassword = true; + }); + }); + var authentication = services.AddAuthentication(options => { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }); + authentication.AddCookie(options => { + options.LoginPath = "/LoginPage"; + }); + authentication.AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters() { + ValidateIssuerSigningKey = true, + //ValidIssuer = Configuration["Authentication:Jwt:Issuer"], + //ValidAudience = Configuration["Authentication:Jwt:Audience"], + ValidateIssuer = false, + ValidateAudience = false, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:Jwt:IssuerSigningKey"])) + }; + }); + services.AddAuthorization(options => { + options.DefaultPolicy = new AuthorizationPolicyBuilder( + JwtBearerDefaults.AuthenticationScheme) + .RequireAuthenticatedUser() + .RequireXafAuthentication() + .Build(); + }); + + services + .AddControllers() + .AddOData((options, serviceProvider) => { + options + .AddRouteComponents("api/odata", new EdmModelBuilder(serviceProvider).GetEdmModel(), + odataServices => { + odataServices.AddSingleton(); + }) + .EnableQueryFeatures(100); + }); + + services.AddSwaggerGen(c => { + c.EnableAnnotations(); + c.SwaggerDoc("v1", new OpenApiInfo { + Title = "DurnyklyYol API", + Version = "v1", + Description = @"Use AddXafWebApi(options) in the DurnyklyYol.Blazor.Server\Startup.cs file to make Business Objects available in the Web API." + }); + c.AddSecurityDefinition("JWT", new OpenApiSecurityScheme() { + Type = SecuritySchemeType.Http, + Name = "Bearer", + Scheme = "bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement() { + { + new OpenApiSecurityScheme() { + Reference = new OpenApiReference() { + Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, + Id = "JWT" + } + }, + new string[0] + }, + }); + }); + + services.Configure(o => { + //The code below specifies that the naming of properties in an object serialized to JSON must always exactly match + //the property names within the corresponding CLR type so that the property names are displayed correctly in the Swagger UI. + //XPO is case-sensitive and requires this setting so that the example request data displayed by Swagger is always valid. + //Comment this code out to revert to the default behavior. + //See the following article for more information: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.propertynamingpolicy + o.JsonSerializerOptions.PropertyNamingPolicy = null; + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + if(env.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "DurnyklyYol WebApi v1"); + }); + } + else { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. To change this for production scenarios, see: https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + app.UseHttpsRedirection(); + app.UseRequestLocalization(); + app.UseStaticFiles(); + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseXaf(); + app.UseEndpoints(endpoints => { + endpoints.MapXafEndpoints(); + endpoints.MapBlazorHub(); + endpoints.MapFallbackToPage("/_Host"); + endpoints.MapControllers(); + }); + } +} diff --git a/DurnyklyYol.Blazor.Server/_Imports.razor b/DurnyklyYol.Blazor.Server/_Imports.razor new file mode 100644 index 0000000..e063a47 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/_Imports.razor @@ -0,0 +1,11 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using DevExpress.Blazor +@using DevExpress.ExpressApp.Blazor.Components +@using DurnyklyYol.Blazor.Server diff --git a/DurnyklyYol.Blazor.Server/appsettings.Development.json b/DurnyklyYol.Blazor.Server/appsettings.Development.json new file mode 100644 index 0000000..437008f --- /dev/null +++ b/DurnyklyYol.Blazor.Server/appsettings.Development.json @@ -0,0 +1,16 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "DevExpress.ExpressApp": "Information" + } + }, + "DevExpress": { + "ExpressApp": { + "EnableDiagnosticActions": false + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/appsettings.json b/DurnyklyYol.Blazor.Server/appsettings.json new file mode 100644 index 0000000..99da02d --- /dev/null +++ b/DurnyklyYol.Blazor.Server/appsettings.json @@ -0,0 +1,62 @@ +{ + "ConnectionStrings": { + "ConnectionString": "XpoProvider=Postgres;server=192.168.99.120;user id=postgres; password=postgres; database=cargo;Encoding=UNICODE", + "ConnectionString2": "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=DurnyklyYol", + "EasyTestConnectionString": "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=DurnyklyYolEasyTest" + }, + "Authentication": { + "Jwt": { + // For more information, refer to the following topic: Configure the JWT Authentication for the Web API https://docs.devexpress.com/eXpressAppFramework/403504 + "Issuer": "My", + "Audience": "http://localhost:4200", + // The debug secret key. You should store sensitive settings in dedicated secret storage. For more information, refer to the following topic: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&tabs=windows. + "IssuerSigningKey": "3e3d5a02-f807-493c-a590-4250c04f2c94" + }, + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "DevExpress.ExpressApp": "Information" + } + }, + "AllowedHosts": "*", + "DevExpress": { + "ExpressApp": { + "Languages": "en-US;tk-TM", + "ShowLanguageSwitcher": true, + "ThemeSwitcher": { + "DefaultItemName": "Office White", + "ShowSizeModeSwitcher": true, + "Groups": [ + { + "Caption": "DevExpress Themes", + "Items": [ + { + "Caption": "Blazing Berry", + "Url": "_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css", + "Color": "#5c2d91" + }, + { + "Caption": "Blazing Dark", + "Url": "_content/DevExpress.Blazor.Themes/blazing-dark.bs5.min.css", + "Color": "#46444a" + }, + { + "Caption": "Office White", + "Url": "_content/DevExpress.Blazor.Themes/office-white.bs5.min.css", + "Color": "#fe7109" + }, + { + "Caption": "Purple", + "Url": "_content/DevExpress.Blazor.Themes/purple.bs5.min.css", + "Color": "#7989ff" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/wwwroot/css/site.css b/DurnyklyYol.Blazor.Server/wwwroot/css/site.css new file mode 100644 index 0000000..2e1c9a0 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/wwwroot/css/site.css @@ -0,0 +1,31 @@ +html, body { + height: 100%; +} + +app { + display: block; + height: 100%; +} + +.header-logo { + flex-shrink: 0; + background-color: currentColor; + -webkit-mask: url('../images/Logo.svg'); + mask: url('../images/Logo.svg'); + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + width: 180px; + height: 24px; +} + +#blazor-error-ui { + background: inherit; + bottom: 0; + display: none; + position: fixed; + width: 100%; + height: 100%; + z-index: 100001; +} \ No newline at end of file diff --git a/DurnyklyYol.Blazor.Server/wwwroot/favicon.ico b/DurnyklyYol.Blazor.Server/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..91de870e5df62f3dc001d46fbbb59522b790e006 GIT binary patch literal 31444 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 literal 0 HcmV?d00001 diff --git a/DurnyklyYol.Blazor.Server/wwwroot/images/Logo.svg b/DurnyklyYol.Blazor.Server/wwwroot/images/Logo.svg new file mode 100644 index 0000000..23480cf --- /dev/null +++ b/DurnyklyYol.Blazor.Server/wwwroot/images/Logo.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/DurnyklyYol.Blazor.Server/wwwroot/images/LogoExpress.svg b/DurnyklyYol.Blazor.Server/wwwroot/images/LogoExpress.svg new file mode 100644 index 0000000..92073b4 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/wwwroot/images/LogoExpress.svg @@ -0,0 +1,41 @@ + + + + diff --git a/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreen.svg b/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreen.svg new file mode 100644 index 0000000..7f661f6 --- /dev/null +++ b/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreen.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreenXAF.svg b/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreenXAF.svg new file mode 100644 index 0000000..0056a3a --- /dev/null +++ b/DurnyklyYol.Blazor.Server/wwwroot/images/SplashScreenXAF.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/DurnyklyYol.MiddleTier/DurnyklyYol.MiddleTier.csproj b/DurnyklyYol.MiddleTier/DurnyklyYol.MiddleTier.csproj new file mode 100644 index 0000000..a89e7fe --- /dev/null +++ b/DurnyklyYol.MiddleTier/DurnyklyYol.MiddleTier.csproj @@ -0,0 +1,27 @@ + + + net8.0 + false + false + 1.0.* + 1.0.0.0 + Debug;Release;EasyTest + enable + + + + + + + + + + + + + + + + + + diff --git a/DurnyklyYol.MiddleTier/JWT/JwtTokenProviderService.cs b/DurnyklyYol.MiddleTier/JWT/JwtTokenProviderService.cs new file mode 100644 index 0000000..4a75de8 --- /dev/null +++ b/DurnyklyYol.MiddleTier/JWT/JwtTokenProviderService.cs @@ -0,0 +1,37 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Runtime.ExceptionServices; +using System.Security.Claims; +using System.Text; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using Microsoft.IdentityModel.Tokens; + +namespace DurnyklyYol.WebApi.JWT; + +public class JwtTokenProviderService : IAuthenticationTokenProvider { + readonly SignInManager signInManager; + readonly IConfiguration configuration; + public JwtTokenProviderService(SignInManager signInManager, IConfiguration configuration) { + this.signInManager = signInManager; + this.configuration = configuration; + } + public string Authenticate(object logonParameters) { + var result = signInManager.AuthenticateByLogonParameters(logonParameters); + if(result.Succeeded) { + var issuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Authentication:Jwt:IssuerSigningKey"])); + var token = new JwtSecurityToken( + //issuer: configuration["Authentication:Jwt:Issuer"], + //audience: configuration["Authentication:Jwt:Audience"], + claims: result.Principal.Claims, + expires: DateTime.Now.AddDays(2), + signingCredentials: new SigningCredentials(issuerSigningKey, SecurityAlgorithms.HmacSha256) + ); + return new JwtSecurityTokenHandler().WriteToken(token); + } + if(result.Error is IUserFriendlyException) { + ExceptionDispatchInfo.Throw(result.Error); + } + throw new AuthenticationException("Internal server error"); + } +} diff --git a/DurnyklyYol.MiddleTier/JWT/WebApiAuthenticationController.cs b/DurnyklyYol.MiddleTier/JWT/WebApiAuthenticationController.cs new file mode 100644 index 0000000..55f7244 --- /dev/null +++ b/DurnyklyYol.MiddleTier/JWT/WebApiAuthenticationController.cs @@ -0,0 +1,29 @@ +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Security.Authentication; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using Microsoft.AspNetCore.Mvc; + +namespace DurnyklyYol.WebApi.JWT; + +[ApiController] +[Route("api/[controller]")] +// This is a JWT authentication service sample. +public class AuthenticationController : ControllerBase { + readonly IAuthenticationTokenProvider tokenProvider; + public AuthenticationController(IAuthenticationTokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + [HttpPost("Authenticate")] + public IActionResult Authenticate( + [FromBody] + AuthenticationStandardLogonParameters logonParameters + ) { + try { + return Ok(tokenProvider.Authenticate(logonParameters)); + } + catch(AuthenticationException ex) { + return Unauthorized(ex.GetJson()); + } + } +} diff --git a/DurnyklyYol.MiddleTier/Program.cs b/DurnyklyYol.MiddleTier/Program.cs new file mode 100644 index 0000000..6ac4179 --- /dev/null +++ b/DurnyklyYol.MiddleTier/Program.cs @@ -0,0 +1,50 @@ +using System.Reflection; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.AspNetCore.DesignTime; +using DevExpress.ExpressApp.Design; +using DevExpress.ExpressApp.Utils; + +namespace DurnyklyYol.WebApi; + +public class Program : IDesignTimeApplicationFactory { + private static bool ContainsArgument(string[] args, string argument) { + return args.Any(arg => arg.TrimStart('/').TrimStart('-').ToLower() == argument.ToLower()); + } + public static int Main(string[] args) { + if(ContainsArgument(args, "help") || ContainsArgument(args, "h")) { + Console.WriteLine("Updates the database when its version does not match the application's version."); + Console.WriteLine(); + Console.WriteLine($" {Assembly.GetExecutingAssembly().GetName().Name}.exe --updateDatabase [--forceUpdate --silent]"); + Console.WriteLine(); + Console.WriteLine("--forceUpdate - Marks that the database must be updated whether its version matches the application's version or not."); + Console.WriteLine("--silent - Marks that database update proceeds automatically and does not require any interaction with the user."); + Console.WriteLine(); + Console.WriteLine($"Exit codes: 0 - {DBUpdaterStatus.UpdateCompleted}"); + Console.WriteLine($" 1 - {DBUpdaterStatus.UpdateError}"); + Console.WriteLine($" 2 - {DBUpdaterStatus.UpdateNotNeeded}"); + } + else { + DevExpress.ExpressApp.FrameworkSettings.DefaultSettingsCompatibilityMode = DevExpress.ExpressApp.FrameworkSettingsCompatibilityMode.Latest; + DevExpress.ExpressApp.Security.SecurityStrategy.AutoAssociationReferencePropertyMode = DevExpress.ExpressApp.Security.ReferenceWithoutAssociationPermissionsMode.AllMembers; + IHost host = CreateHostBuilder(args).Build(); + if(ContainsArgument(args, "updateDatabase")) { + using(var serviceScope = host.Services.CreateScope()) { + return serviceScope.ServiceProvider.GetRequiredService().Update(ContainsArgument(args, "forceUpdate"), ContainsArgument(args, "silent")); + } + } + else { + host.Run(); + } + } + return 0; + } + XafApplication IDesignTimeApplicationFactory.Create() { + IHostBuilder hostBuilder = CreateHostBuilder(Array.Empty()); + return DesignTimeApplicationFactoryHelper.Create(hostBuilder); + } + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { + webBuilder.UseStartup(); + }); +} diff --git a/DurnyklyYol.MiddleTier/Properties/launchSettings.json b/DurnyklyYol.MiddleTier/Properties/launchSettings.json new file mode 100644 index 0000000..fa148ed --- /dev/null +++ b/DurnyklyYol.MiddleTier/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5000", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "MiddleTier/Index", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DurnyklyYol.MiddleTier": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchUrl": "MiddleTier/Index", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.MiddleTier/ReadMe.txt b/DurnyklyYol.MiddleTier/ReadMe.txt new file mode 100644 index 0000000..2ba7100 --- /dev/null +++ b/DurnyklyYol.MiddleTier/ReadMe.txt @@ -0,0 +1,15 @@ +Project Description + +This project implements an ASP.NET Core Web API application (https://docs.devexpress.com/eXpressAppFramework/403394/backend-web-api-service). +The appsettings.json file contains database connection, logging, and authentication settings (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration). + +YOUR FEEDBACK MATTERS +Please tell us how our Web API Service works for you: https://www.devexpress.com/go/XAF_WebAPI_Feedback.aspx - THANKS! + +Relevant Documentation + +Backend Web API Service +https://docs.devexpress.com/eXpressAppFramework/403394/backend-web-api-service + +Overview +https://www.devexpress.com/products/net/application_framework/security-web-api-service.xml \ No newline at end of file diff --git a/DurnyklyYol.MiddleTier/Startup.cs b/DurnyklyYol.MiddleTier/Startup.cs new file mode 100644 index 0000000..5b8bbf8 --- /dev/null +++ b/DurnyklyYol.MiddleTier/Startup.cs @@ -0,0 +1,125 @@ +using DevExpress.ExpressApp.Security; +using DevExpress.Persistent.Base; +using DevExpress.ExpressApp.Xpo; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using DurnyklyYol.WebApi.JWT; +using DevExpress.ExpressApp.Security.Authentication.ClientServer; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.ApplicationBuilder; + +namespace DurnyklyYol.WebApi; + +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. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) { + services.AddScoped(); + + services.AddXafMiddleTier(Configuration, builder => { + builder.ConfigureDataServer(options => { + options.UseConnectionString(Configuration.GetConnectionString("ConnectionString")); + options.UseDataStorePool(true); + }); + + builder.Modules + .AddReports(options => { + options.ReportDataType = typeof(DevExpress.Persistent.BaseImpl.ReportDataV2); + }) + .Add(); + + + builder.Security + .UseIntegratedMode(options => { + options.Lockout.Enabled = true; + + options.RoleType = typeof(PermissionPolicyRole); + // ApplicationUser descends from PermissionPolicyUser and supports the OAuth authentication. For more information, refer to the following topic: https://docs.devexpress.com/eXpressAppFramework/402197 + // If your application uses PermissionPolicyUser or a custom user type, set the UserType property as follows: + options.UserType = typeof(DurnyklyYol.Module.BusinessObjects.ApplicationUser); + // ApplicationUserLoginInfo is only necessary for applications that use the ApplicationUser user type. + // If you use PermissionPolicyUser or a custom user type, comment out the following line: + options.UserLoginInfoType = typeof(DurnyklyYol.Module.BusinessObjects.ApplicationUserLoginInfo); + options.UseXpoPermissionsCaching(); + options.Events.OnSecurityStrategyCreated += securityStrategy => { + //((SecurityStrategy)securityStrategy).AnonymousAllowedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.ModelDifference)); + //((SecurityStrategy)securityStrategy).AnonymousAllowedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.ModelDifferenceAspect)); + ((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.CacheOnFirstAccess; + }; + }) + .AddPasswordAuthentication(options => { + options.IsSupportChangePassword = true; + }); + + builder.AddBuildStep(application => { + application.ApplicationName = "SetupApplication.DurnyklyYol"; + application.CheckCompatibilityType = DevExpress.ExpressApp.CheckCompatibilityType.DatabaseSchema; + //application.CreateCustomModelDifferenceStore += += (s, e) => { + // e.Store = new ModelDifferenceDbStore((XafApplication)sender!, typeof(ModelDifference), true, "Win"); + // e.Handled = true; + //}; +#if !RELEASE + if(application.CheckCompatibilityType == CheckCompatibilityType.DatabaseSchema) { + application.DatabaseUpdateMode = DatabaseUpdateMode.UpdateDatabaseAlways; + application.DatabaseVersionMismatch += (s, e) => { + e.Updater.Update(); + e.Handled = true; + }; + } +#endif + }); + }); + + services.AddAuthentication() + .AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters() { + ValidateIssuerSigningKey = true, + //ValidIssuer = Configuration["Authentication:Jwt:Issuer"], + //ValidAudience = Configuration["Authentication:Jwt:Audience"], + ValidateIssuer = false, + ValidateAudience = false, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:Jwt:IssuerSigningKey"])) + }; + }); + + services.AddAuthorization(options => { + options.DefaultPolicy = new AuthorizationPolicyBuilder( + JwtBearerDefaults.AuthenticationScheme) + .RequireAuthenticatedUser() + .RequireXafAuthentication() + .Build(); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime hostApplicationLifetime) { + if(env.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + } + else { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. To change this for production scenarios, see: https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + app.UseHttpsRedirection(); + app.UseRequestLocalization(); + app.UseStaticFiles(); + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseXafMiddleTier(); + app.UseEndpoints(endpoints => { + endpoints.MapControllers(); + }); + } +} diff --git a/DurnyklyYol.MiddleTier/appsettings.Development.json b/DurnyklyYol.MiddleTier/appsettings.Development.json new file mode 100644 index 0000000..437008f --- /dev/null +++ b/DurnyklyYol.MiddleTier/appsettings.Development.json @@ -0,0 +1,16 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "DevExpress.ExpressApp": "Information" + } + }, + "DevExpress": { + "ExpressApp": { + "EnableDiagnosticActions": false + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.MiddleTier/appsettings.json b/DurnyklyYol.MiddleTier/appsettings.json new file mode 100644 index 0000000..9858f63 --- /dev/null +++ b/DurnyklyYol.MiddleTier/appsettings.json @@ -0,0 +1,29 @@ +{ + "ConnectionStrings": { + "ConnectionString": "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=DurnyklyYol", + "EasyTestConnectionString": "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=DurnyklyYolEasyTest" + }, + "Authentication": { + "Jwt": { + // For more information, refer to the following topic: Configure the JWT Authentication for the Web API https://docs.devexpress.com/eXpressAppFramework/403504 + "Issuer": "My", + "Audience": "http://localhost:4200", + // The debug secret key. You should store sensitive settings in dedicated secret storage. For more information, refer to the following topic: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&tabs=windows. + "IssuerSigningKey": "3e3d5a02-f807-493c-a590-4250c04f2c94" + }, + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "DevExpress.ExpressApp": "Information" + } + }, + "AllowedHosts": "*", + "DevExpress": { + "ExpressApp": { + "Languages": "en-US;" + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/ApplicationUser.cs b/DurnyklyYol.Module/BusinessObjects/ApplicationUser.cs new file mode 100644 index 0000000..136e772 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/ApplicationUser.cs @@ -0,0 +1,45 @@ +using System.ComponentModel; +using System.Text; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Security; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DevExpress.Xpo; + +namespace DurnyklyYol.Module.BusinessObjects; + +[MapInheritance(MapInheritanceType.ParentTable)] +[DefaultProperty(nameof(UserName))] +public class ApplicationUser : PermissionPolicyUser, ISecurityUserWithLoginInfo, ISecurityUserLockout { + private int accessFailedCount; + private DateTime lockoutEnd; + + public ApplicationUser(Session session) : base(session) { } + + [Browsable(false)] + public int AccessFailedCount { + get { return accessFailedCount; } + set { SetPropertyValue(nameof(AccessFailedCount), ref accessFailedCount, value); } + } + + [Browsable(false)] + public DateTime LockoutEnd { + get { return lockoutEnd; } + set { SetPropertyValue(nameof(LockoutEnd), ref lockoutEnd, value); } + } + + [Browsable(false)] + [Aggregated, Association("User-LoginInfo")] + public XPCollection LoginInfo { + get { return GetCollection(nameof(LoginInfo)); } + } + + IEnumerable IOAuthSecurityUser.UserLogins => LoginInfo.OfType(); + + ISecurityUserLoginInfo ISecurityUserWithLoginInfo.CreateUserLoginInfo(string loginProviderName, string providerUserKey) { + ApplicationUserLoginInfo result = new ApplicationUserLoginInfo(Session); + result.LoginProviderName = loginProviderName; + result.ProviderUserKey = providerUserKey; + result.User = this; + return result; + } +} diff --git a/DurnyklyYol.Module/BusinessObjects/ApplicationUserLoginInfo.cs b/DurnyklyYol.Module/BusinessObjects/ApplicationUserLoginInfo.cs new file mode 100644 index 0000000..0e93b72 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/ApplicationUserLoginInfo.cs @@ -0,0 +1,36 @@ +using DevExpress.ExpressApp.ConditionalAppearance; +using DevExpress.ExpressApp.Security; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Xpo; + +namespace DurnyklyYol.Module.BusinessObjects; + +[DeferredDeletion(false)] +[Persistent("PermissionPolicyUserLoginInfo")] +public class ApplicationUserLoginInfo : BaseObject, ISecurityUserLoginInfo { + private string loginProviderName; + private ApplicationUser user; + private string providerUserKey; + public ApplicationUserLoginInfo(Session session) : base(session) { } + + [Indexed("ProviderUserKey", Unique = true)] + [Appearance("PasswordProvider", Enabled = false, Criteria = "!(IsNewObject(this)) and LoginProviderName == '" + SecurityDefaults.PasswordAuthentication + "'", Context = "DetailView")] + public string LoginProviderName { + get { return loginProviderName; } + set { SetPropertyValue(nameof(LoginProviderName), ref loginProviderName, value); } + } + + [Appearance("PasswordProviderUserKey", Enabled = false, Criteria = "!(IsNewObject(this)) and LoginProviderName == '" + SecurityDefaults.PasswordAuthentication + "'", Context = "DetailView")] + public string ProviderUserKey { + get { return providerUserKey; } + set { SetPropertyValue(nameof(ProviderUserKey), ref providerUserKey, value); } + } + + [Association("User-LoginInfo")] + public ApplicationUser User { + get { return user; } + set { SetPropertyValue(nameof(User), ref user, value); } + } + + object ISecurityUserLoginInfo.User => User; +} diff --git a/DurnyklyYol.Module/BusinessObjects/Cargo.cs b/DurnyklyYol.Module/BusinessObjects/Cargo.cs new file mode 100644 index 0000000..2076595 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Cargo.cs @@ -0,0 +1,208 @@ +using DevExpress.ExpressApp.ConditionalAppearance; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System.ComponentModel; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Transportation")] + [ImageName("Shipment")] + [DefaultProperty(nameof(No))] + [Appearance("CargoRedBackground", AppearanceItemType = "ViewItem", TargetItems = "Credit", Criteria = "TotalСredit > 0", BackColor = "Red", FontColor = "White")] + [Appearance("CargoStrikeBackground", AppearanceItemType = "ViewItem", TargetItems = "*", Criteria = "TotalСredit = 0 And TotalPriceAmount >0 ", FontStyle = DevExpress.Drawing.DXFontStyle.Strikeout)] + public class Cargo : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Cargo(Session session) + : base(session) + { + } + + protected override void OnSaving() + { + base.OnSaving(); + if (Session is not NestedUnitOfWork + && (Session.DataLayer != null) + && Session.IsNewObject(this) + && (Session.ObjectLayer is SimpleObjectLayer) + //OR + //&& !(Session.ObjectLayer is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer) + && string.IsNullOrEmpty(No)) + { + int nextSequence = DistributedIdGeneratorHelper.Generate(Session.DataLayer, this.GetType().FullName, "ERPrefix"); + No = string.Format("KA-{0:D6}", nextSequence); + } + } + double totalWeight; + string no; + //CargoState state; + Point currentPoint; + DateTime? arrivedAt; + DateTime? startedAt; + Carrier carrier; + Route route; + + [ReadOnly(true), VisibleInDetailView(false), Index(0)] + [Size(SizeAttribute.DefaultStringMappingFieldSize), Indexed, RuleUniqueValue] + public string No + { + get => no; + set => SetPropertyValue(nameof(No), ref no, value); + } + //TODO current root bilen deneshdirip al + public CargoState State + { + get + { + if (CurrentPoint == null || CurrentPoint.Oid == Route.StartPoint.Point.Oid) + { + return CargoState.Warehouse; + } + else if (CurrentPoint != null && CurrentPoint.Oid == Route.DestinationPoint.Point.Oid) + { + return CargoState.Arrived; + } + else + { + return CargoState.InTransit; + } + } + //set => SetPropertyValue(nameof(State), ref state, value); + } + [ModelDefault("DisplayFormat", "{0:g}"), ModelDefault("EditMask", "g")] + public DateTime? StartedAt + { + get => startedAt; + set => SetPropertyValue(nameof(StartedAt), ref startedAt, value); + } + [ModelDefault("DisplayFormat", "{0:g}"), ModelDefault("EditMask", "g")] + public DateTime? ArrivedAt + { + get => arrivedAt; + set => SetPropertyValue(nameof(ArrivedAt), ref arrivedAt, value); + } + + + public double TotalWeight + { + get => totalWeight; + set => SetPropertyValue(nameof(TotalWeight), ref totalWeight, value); + } + + //[Persistent(nameof(TotalPriceAmount))] + //private decimal totalPriceAmount; + + //[PersistentAlias(nameof(totalPriceAmount))] + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal TotalPriceAmount => Goods.Sum(gs => gs.Price); + + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal TotalExpenseAmount => Expenses.Sum(es => es.Amount); + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal PayedTotal => Goods.Sum(gs => gs.TotalPayment); + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal TotalСredit => Goods.Sum(gs => gs.Credit); + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal ExpectedProfit => TotalPriceAmount - TotalExpenseAmount; + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal CurrentProfit => PayedTotal - TotalExpenseAmount; + + [Browsable(false)] + public string StartsFrom => Route?.StartPoint?.Name; + [Browsable(false)] + public string DestinationTo => Route?.DestinationPoint?.Name; + + [RuleRequiredField(DefaultContexts.Save), ImmediatePostData] + public Route Route + { + get => route; + set { + if (SetPropertyValue(nameof(Route), ref route, value) && !IsLoading && !IsSaving && value is not null) + { + CurrentPoint = null; + OnChanged(nameof(CurrentPoint)); + } + } + } + + private void populateCargoPoints() + { + //todo initialize cargo route points + //current point is taken from cargo route points where date is not null + } + + [Browsable(false)] + public XPCollection AvailablePoints + { + get + { + if (Route is null) return new XPCollection(Session); + + var startPoint = Route.StartPoint.Point; + var endPoint = Route.DestinationPoint.Point; + + var points = Route.Points + .OrderBy(or => or.Order) + .Select(sl => sl.Point) + .ToList(); + + points.Insert(0, startPoint); + points.Add(endPoint); + + return new XPCollection(Session, points); + } + } + + [RuleRequiredField(DefaultContexts.Save), ImmediatePostData, DataSourceProperty(nameof(AvailablePoints))] + public Point CurrentPoint + { + get => currentPoint; + set => SetPropertyValue(nameof(CurrentPoint), ref currentPoint, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + public Carrier Carrier + { + get => carrier; + set => SetPropertyValue(nameof(Carrier), ref carrier, value); + } + + [Association("Cargo-Goods")] + public XPCollection Goods + { + get + { + return GetCollection(nameof(Goods)); + } + } + + [Association("Cargo-Expenses"), Aggregated] + public XPCollection Expenses + { + get + { + return GetCollection(nameof(Expenses)); + } + } + + [Association, Aggregated] + public XPCollection CargoPoints + { + get + { + return GetCollection(nameof(CargoPoints)); + } + } + } + + public enum CargoState + { + Warehouse, + InTransit, + Arrived + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/CargoRoutePoints.cs b/DurnyklyYol.Module/BusinessObjects/CargoRoutePoints.cs new file mode 100644 index 0000000..df3f4c0 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/CargoRoutePoints.cs @@ -0,0 +1,54 @@ +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Xpo; +using System.ComponentModel; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, CreatableItem(false), NavigationItem(false), DeferredDeletion(false)] + + public class CargoRoutePoints : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public CargoRoutePoints(Session session) + : base(session) + { + } + + + Cargo cargo; + DateTime? date; + Point point; + ushort order; + + [Index(0), ReadOnly(true)] + public ushort Order + { + get => order; + set => SetPropertyValue(nameof(Order), ref order, value); + } + + [ReadOnly(true), Index(1)] + public Point Point + { + get => point; + set => SetPropertyValue(nameof(Point), ref point, value); + } + + [Index(2)] + public DateTime? Date + { + get => date; + set => SetPropertyValue(nameof(Date), ref date, value); + } + + + [Association, Browsable(false)] + public Cargo Cargo + { + get => cargo; + set => SetPropertyValue(nameof(Cargo), ref cargo, value); + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Carrier.cs b/DurnyklyYol.Module/BusinessObjects/Carrier.cs new file mode 100644 index 0000000..a859de3 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Carrier.cs @@ -0,0 +1,86 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Transportation")] + [ImageName("Gauges")] + [DefaultProperty(nameof(Name)), FriendlyKeyProperty(nameof(Number))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Carrier : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Carrier(Session session) + : base(session) + { + } + + + string phone; + uint weight; + string notes; + string driver; + string number; + string name; + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Number + { + get => number; + set => SetPropertyValue(nameof(Number), ref number, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Driver + { + get => driver; + set => SetPropertyValue(nameof(Driver), ref driver, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Phone + { + get => phone; + set => SetPropertyValue(nameof(Phone), ref phone, value); + } + + [ModelDefault("DisplayFormat", "{0:n2} kg")] + public uint Weight + { + get => weight; + set => SetPropertyValue(nameof(Weight), ref weight, value); + } + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Notes + { + get => notes; + set => SetPropertyValue(nameof(Notes), ref notes, value); + } + + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Client.cs b/DurnyklyYol.Module/BusinessObjects/Client.cs new file mode 100644 index 0000000..5e3d2e1 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Client.cs @@ -0,0 +1,104 @@ +using DevExpress.Data.Filtering; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Clients & Goods")] + [ImageName("BO_Customer")] + [DefaultProperty(nameof(FullName))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Client : ApplicationUser + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Client(Session session) + : base(session) + { + } + protected override void OnSaving() + { + base.OnSaving(); + if (Session.IsNewObject(this)) + { + AssignClientRole(); + } + } + private void AssignClientRole() + { + // Find the "Client" role in the database + var clientRole = Session.FindObject( + CriteriaOperator.Parse("Name == ?", GlobalConstants.ClientRoleName)); + + // If the role is found, assign it to the user + if (clientRole != null) + { + this.Roles.Add(clientRole); + } + } + string bellik; + string telefon; + Region region; + string fullName; + //string phoneNo; + + + //[Size(8), ] + //public string PhoneNo + //{ + // get => phoneNo; + // set => SetPropertyValue(nameof(PhoneNo), ref phoneNo, value); + //} + + [Index(2)] + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Bellik + { + get => bellik; + set => SetPropertyValue(nameof(Bellik), ref bellik, value); + } + + [Index(1)] + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Telefon + { + get => telefon; + set => SetPropertyValue(nameof(Telefon), ref telefon, value); + } + + [Index(0)] + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string FullName + { + get => fullName; + set => SetPropertyValue(nameof(FullName), ref fullName, value); + } + + [IgnoreDataMember] + public Region Region + { + get => region; + set => SetPropertyValue(nameof(Region), ref region, value); + } + + [Association("Client-Goods"), Aggregated, IgnoreDataMember] + public XPCollection Goods + { + get + { + return GetCollection(nameof(Goods)); + } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Contact.cs b/DurnyklyYol.Module/BusinessObjects/Contact.cs new file mode 100644 index 0000000..7a635a8 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Contact.cs @@ -0,0 +1,51 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions] + [ImageName("BO_Contact")] + [DefaultProperty("Name")] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Contact : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Contact(Session session) + : base(session) + { + } + + + string name; + string number; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Number + { + get => number; + set => SetPropertyValue(nameof(Number), ref number, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Expense.cs b/DurnyklyYol.Module/BusinessObjects/Expense.cs new file mode 100644 index 0000000..78b4c49 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Expense.cs @@ -0,0 +1,98 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Finance")] + [ImageName("BO_Invoice")] + [DefaultProperty(nameof(No))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Expense : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Expense(Session session) + : base(session) + { + } + public override void AfterConstruction() + { + base.AfterConstruction(); + ExpenseDate = DateTime.Now; + } + + protected override void OnSaving() + { + base.OnSaving(); + if (Session is not NestedUnitOfWork + && (Session.DataLayer != null) + && Session.IsNewObject(this) + && (Session.ObjectLayer is SimpleObjectLayer) + //OR + //&& !(Session.ObjectLayer is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer) + && string.IsNullOrEmpty(No)) + { + int nextSequence = DistributedIdGeneratorHelper.Generate(Session.DataLayer, this.GetType().FullName, "ERPrefix"); + No = string.Format("ÇD-{0:D6}", nextSequence); + } + } + + string description; + string no; + decimal amount; + Cargo cargo; + DateTime expenseDate; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleUniqueValue, Indexed(Unique = true), ReadOnly(true), VisibleInDetailView(false)] + public string No + { + get => no; + set => SetPropertyValue(nameof(No), ref no, value); + } + + [RuleValueComparison(ValueComparisonType.GreaterThan, 0)] + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal Amount + { + get => amount; + set => SetPropertyValue(nameof(Amount), ref amount, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + [ModelDefault("DisplayFormat", "{0:g}"), ModelDefault("EditMask", "g")] + public DateTime ExpenseDate + { + get => expenseDate; + set => SetPropertyValue(nameof(ExpenseDate), ref expenseDate, value); + } + + + [Association("Cargo-Expenses"), RuleRequiredField(DefaultContexts.Save)] + public Cargo Cargo + { + get => cargo; + set => SetPropertyValue(nameof(Cargo), ref cargo, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Description + { + get => description; + set => SetPropertyValue(nameof(Description), ref description, value); + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Goods.cs b/DurnyklyYol.Module/BusinessObjects/Goods.cs new file mode 100644 index 0000000..4d271a6 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Goods.cs @@ -0,0 +1,241 @@ +using DevExpress.ExpressApp.ConditionalAppearance; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using FileSystemData.BusinessObjects; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Clients & Goods")] + [ImageName("BO_Product")] + [DefaultProperty(nameof(No))] + [Appearance("RedBackground", Priority =1, AppearanceItemType = "ViewItem", TargetItems = "Credit", Criteria = "Credit > 0", BackColor = "Red", FontColor = "White")] + [Appearance("StrikeBackground", Priority = 2, AppearanceItemType = "ViewItem", TargetItems = "Credit", Criteria = "Credit = 0", BackColor = "Green",FontColor = "White")] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Goods : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Goods(Session session) + : base(session) + { + } + protected override void OnSaving() + { + base.OnSaving(); + if (Session is not NestedUnitOfWork + && (Session.DataLayer != null) + && Session.IsNewObject(this) + && (Session.ObjectLayer is SimpleObjectLayer) + //OR + //&& !(Session.ObjectLayer is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer) + && string.IsNullOrEmpty(No)) + { + int nextSequence = DistributedIdGeneratorHelper.Generate(Session.DataLayer, this.GetType().FullName, "ERPrefix"); + No = string.Format("PA-{0:D6}", nextSequence); + } + } + public override void AfterConstruction() + { + base.AfterConstruction(); + State = GoodsState.Reserved; + CreatedAt = DateTime.Now; + } + + [Size(SizeAttribute.DefaultStringMappingFieldSize), NonCloneable, Indexed(Unique = true), ReadOnly(true), VisibleInDetailView(false)] + public string No + { + get => no; + set => SetPropertyValue(nameof(No), ref no, value); + } + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save), Index(2), VisibleInLookupListView(true)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + + [ModelDefault("DisplayFormat", "{0:g}"), ModelDefault("EditMask", "g"), ReadOnly(true)] + public DateTime CreatedAt + { + get => createdAt; + set => SetPropertyValue(nameof(CreatedAt), ref createdAt, value); + } + + [RuleValueComparison(ValueComparisonType.GreaterThan, 0)] + public double Volume + { + get => volume; + set => SetPropertyValue(nameof(Volume), ref volume, value); + } + + [RuleValueComparison(ValueComparisonType.GreaterThan, 0)] + public uint PlaceCount + { + get => placeCount; + set => SetPropertyValue(nameof(PlaceCount), ref placeCount, value); + } + + [RuleValueComparison(ValueComparisonType.GreaterThan, 0)] + public uint PackageCount + { + get => packageCount; + set => SetPropertyValue(nameof(PackageCount), ref packageCount, value); + } + + [Size(SizeAttribute.DefaultStringMappingFieldSize), IgnoreDataMember] + public string PackingType + { + get => packingType; + set => SetPropertyValue(nameof(PackingType), ref packingType, value); + } + + public DateTime ReceivedAt + { + get => receivedAt; + set => SetPropertyValue(nameof(ReceivedAt), ref receivedAt, value); + } + + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal Price + { + get => price; + set => SetPropertyValue(nameof(Price), ref price, value); + } + + public uint Height + { + get => height; + set => SetPropertyValue(nameof(Height), ref height, value); + } + + public uint Width + { + get => width; + set => SetPropertyValue(nameof(Width), ref width, value); + } + + public uint Depth + { + get => depth; + set => SetPropertyValue(nameof(Depth), ref depth, value); + } + + //[RuleRequiredField(DefaultContexts.Save), Index(1)] + //public Shop Shop + //{ + // get => shop; + // set => SetPropertyValue(nameof(Shop), ref shop, value); + //} + + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string ShopNo + { + get => shopNo; + set => SetPropertyValue(nameof(ShopNo), ref shopNo, value); + } + + [Aggregated, ExpandObjectMembers(ExpandObjectMembers.Never), ImmediatePostData] + public FileSystemStoreObject Image1 + { + get { return GetPropertyValue("Image1"); } + set { SetPropertyValue("Image1", value); } + } + + [Aggregated, ExpandObjectMembers(ExpandObjectMembers.Never), ImmediatePostData] + public FileSystemStoreObject Image2 + { + get { return GetPropertyValue("Image2"); } + set { SetPropertyValue("Image2", value); } + } + + [Aggregated, ExpandObjectMembers(ExpandObjectMembers.Never), ImmediatePostData] + public FileSystemStoreObject Image3 + { + get { return GetPropertyValue("Image3"); } + set { SetPropertyValue("Image3", value); } + } + + [Association("Goods-Payments"), Aggregated] + public XPCollection Payments + { + get + { + return GetCollection(nameof(Payments)); + } + } + + [Association("Goods-GoodsImages"), Aggregated] + public XPCollection Images + { + get { return GetCollection(nameof(Images)); } + } + + [Index(0), VisibleInLookupListView(true), VisibleInDetailView(true), IgnoreDataMember] + [Association("Client-Goods"), RuleRequiredField(DefaultContexts.Save)] + public Client Client + { + get => client; + set => SetPropertyValue(nameof(Client), ref client, value); + } + + [VisibleInDetailView(false)] + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal TotalPayment => Payments.Sum(ps => ps.Amount); + [VisibleInDetailView(false)] + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal Credit => Price - TotalPayment; + + + public GoodsState State + { + get => state; + set => SetPropertyValue(nameof(State), ref state, value); + } + + string shopNo; + DateTime createdAt; + GoodsState state; + //Shop shop; + uint depth; + uint width; + uint height; + uint packageCount; + string packingType; + DateTime receivedAt; + decimal price; + uint placeCount; + double volume; + string name; + string no; + Client client; + Cargo cargo; + + [Association("Cargo-Goods")] + public Cargo Cargo + { + get => cargo; + set => SetPropertyValue(nameof(Cargo), ref cargo, value); + } + } + + public enum GoodsState + { + Reserved, + Received, + Delivered, + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/GoodsImage.cs b/DurnyklyYol.Module/BusinessObjects/GoodsImage.cs new file mode 100644 index 0000000..f68a7aa --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/GoodsImage.cs @@ -0,0 +1,39 @@ +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Xpo; +using FileSystemData.BusinessObjects; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, CreatableItem(false), NavigationItem(false), DeferredDeletion(false)] + [FileAttachment(nameof(File))] + //[ImageName("BO_Contact")] + //[DefaultProperty("DisplayMemberNameForLookupEditorsOfThisType")] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class GoodsImage : FileSystemStoreObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public GoodsImage(Session session) + : base(session) + { + } + private Goods goods; + [Association("Goods-GoodsImages")] + public Goods Goods + { + get { return goods; } + set { SetPropertyValue(nameof(Goods), ref goods, value); } + } + private FileData file; + [FileTypeFilter("ImageFiles", 1, "*.png", "*.jpg")] + [Aggregated, ExpandObjectMembers(ExpandObjectMembers.Never), ImmediatePostData] + public FileData File + { + get { return file; } + set { SetPropertyValue(nameof(File), ref file, value); } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Payment.cs b/DurnyklyYol.Module/BusinessObjects/Payment.cs new file mode 100644 index 0000000..3da4dda --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Payment.cs @@ -0,0 +1,95 @@ +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Finance")] + [ImageName("Refund")] + [DefaultProperty(nameof(No))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Payment : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Payment(Session session) + : base(session) + { + } + public override void AfterConstruction() + { + base.AfterConstruction(); + PaymentDate = DateTime.Now; + } + + protected override void OnSaving() + { + base.OnSaving(); + if (Session is not NestedUnitOfWork + && (Session.DataLayer != null) + && Session.IsNewObject(this) + && (Session.ObjectLayer is SimpleObjectLayer) + //OR + //&& !(Session.ObjectLayer is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer) + && string.IsNullOrEmpty(No)) + { + int nextSequence = DistributedIdGeneratorHelper.Generate(Session.DataLayer, this.GetType().FullName, "ERPrefix"); + No = string.Format("GD-{0:D6}", nextSequence); + } + } + string description; + string no; + decimal amount; + Goods goods; + DateTime paymentDate; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleUniqueValue,Indexed(Unique = true), RuleUniqueValue, ReadOnly(true), VisibleInDetailView(false)] + public string No + { + get => no; + set => SetPropertyValue(nameof(No), ref no, value); + } + + [RuleValueComparison(ValueComparisonType.GreaterThan, 0)] + [ModelDefault("DisplayFormat", "${0:#,##0.00}")] + public decimal Amount + { + get => amount; + set => SetPropertyValue(nameof(Amount), ref amount, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + [ModelDefault("DisplayFormat", "{0:g}"), ModelDefault("EditMask", "g")] + public DateTime PaymentDate + { + get => paymentDate; + set => SetPropertyValue(nameof(PaymentDate), ref paymentDate, value); + } + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Description + { + get => description; + set => SetPropertyValue(nameof(Description), ref description, value); + } + + [RuleRequiredField(DefaultContexts.Save), ImmediatePostData] + [Association("Goods-Payments")] + public Goods Goods + { + get => goods; + set => SetPropertyValue(nameof(Goods), ref goods, value); + } + + public Client Client => Goods?.Client; + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Point.cs b/DurnyklyYol.Module/BusinessObjects/Point.cs new file mode 100644 index 0000000..a12d32e --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Point.cs @@ -0,0 +1,58 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Places")] + [ImageName("Travel_MapPointer")] + [DefaultProperty(nameof(Name))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Point : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Point(Session session) + : base(session) + { + } + + double longitude; + double latitude; + string name; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save), Indexed, RuleUniqueValue] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + + //todo edit mask + public double Latitude + { + get => latitude; + set => SetPropertyValue(nameof(Latitude), ref latitude, value); + } + + //todo edit mask + public double Longitude + { + get => longitude; + set => SetPropertyValue(nameof(Longitude), ref longitude, value); + } + + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/ReadMe.txt b/DurnyklyYol.Module/BusinessObjects/ReadMe.txt new file mode 100644 index 0000000..a958910 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/ReadMe.txt @@ -0,0 +1,26 @@ +Folder Description + +The "BusinessObjects" project folder is intended for storing code of your data model. +In XAF, a business object can be implemented as an ORM-based persistent class +or a non-persistent POCO. + + +Relevant Documentation + +Business Model Design +https://docs.devexpress.com/eXpressAppFramework/113461 + +Non-Persistent Objects +https://docs.devexpress.com/eXpressAppFramework/116516 + +Data Types Supported by built-in Editors +https://docs.devexpress.com/eXpressAppFramework/113014 + +Ways to Add a Business Class (XPO) +https://docs.devexpress.com/eXpressAppFramework/112847 + +Ways to Implement Business Logic +https://docs.devexpress.com/eXpressAppFramework/113710 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Module/BusinessObjects/Region.cs b/DurnyklyYol.Module/BusinessObjects/Region.cs new file mode 100644 index 0000000..fe7fbc0 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Region.cs @@ -0,0 +1,41 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Places")] + [ImageName("BO_Country_v92")] + [DefaultProperty(nameof(Name))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Region : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Region(Session session) + : base(session) + { + } + + string name; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), Indexed, RuleUniqueValue, RuleRequiredField(DefaultContexts.Save)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Route.cs b/DurnyklyYol.Module/BusinessObjects/Route.cs new file mode 100644 index 0000000..cbcc464 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Route.cs @@ -0,0 +1,61 @@ +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Transportation")] + [ImageName("GeoPointMaps")] + [DefaultProperty(nameof(Title))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Route : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Route(Session session) + : base(session) + { + } + + + Warehouse destinationPoint; + Warehouse startPoint; + string title; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Title + { + get => title; + set => SetPropertyValue(nameof(Title), ref title, value); + } + [RuleRequiredField(DefaultContexts.Save)] + public Warehouse StartPoint + { + get => startPoint; + set => SetPropertyValue(nameof(StartPoint), ref startPoint, value); + } + [RuleRequiredField(DefaultContexts.Save)] + public Warehouse DestinationPoint + { + get => destinationPoint; + set => SetPropertyValue(nameof(DestinationPoint), ref destinationPoint, value); + } + + [Association("Route-RoutePoints"), Aggregated] + public XPCollection Points + { + get + { + return GetCollection(nameof(Points)); + } + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/RoutePoint.cs b/DurnyklyYol.Module/BusinessObjects/RoutePoint.cs new file mode 100644 index 0000000..2bdd423 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/RoutePoint.cs @@ -0,0 +1,60 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem(false)] + [DeferredDeletion(false), CreatableItem(false)] + //[ImageName("BO_Contact")] + //[DefaultProperty("DisplayMemberNameForLookupEditorsOfThisType")] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class RoutePoint : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public RoutePoint(Session session) + : base(session) + { + } + + + Point point; + ushort order; + Route route; + + [Association("Route-RoutePoints")] + public Route Route + { + get => route; + set => SetPropertyValue(nameof(Route), ref route, value); + } + + [RuleValueComparison(ValueComparisonType.LessThan, ushort.MaxValue)] + public ushort Order + { + get => order; + set => SetPropertyValue(nameof(Order), ref order, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + public Point Point + { + get => point; + set => SetPropertyValue(nameof(Point), ref point, value); + } + + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Shop.cs b/DurnyklyYol.Module/BusinessObjects/Shop.cs new file mode 100644 index 0000000..010c680 --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Shop.cs @@ -0,0 +1,61 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem(false), CreatableItem(false)] + [ImageName("Shopping_Store")] + [DefaultProperty(nameof(No))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Shop : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Shop(Session session) + : base(session) + { + } + + string phone; + string name; + string no; + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save), RuleUniqueValue] + public string No + { + get => no; + set => SetPropertyValue(nameof(No), ref no, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + + + [Size(SizeAttribute.DefaultStringMappingFieldSize)] + public string Phone + { + get => phone; + set => SetPropertyValue(nameof(Phone), ref phone, value); + } + + + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/BusinessObjects/Warehouse.cs b/DurnyklyYol.Module/BusinessObjects/Warehouse.cs new file mode 100644 index 0000000..428d20c --- /dev/null +++ b/DurnyklyYol.Module/BusinessObjects/Warehouse.cs @@ -0,0 +1,64 @@ + +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using DevExpress.Xpo; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.BusinessObjects +{ + [DefaultClassOptions, NavigationItem("Places")] + [ImageName("BO_Organization")] + [DefaultProperty(nameof(Name))] + //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)] + //[Persistent("DatabaseTableName")] + // Specify more UI options using a declarative approach (https://documentation.devexpress.com/#eXpressAppFramework/CustomDocument112701). + public class Warehouse : BaseObject + { // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx). + // Use CodeRush to create XPO classes and properties with a few keystrokes. + // https://docs.devexpress.com/CodeRushForRoslyn/118557 + public Warehouse(Session session) + : base(session) + { + } + + + string name; + Point point; + string address; + Region region; + + + [Size(SizeAttribute.DefaultStringMappingFieldSize), RuleRequiredField(DefaultContexts.Save)] + public string Name + { + get => name; + set => SetPropertyValue(nameof(Name), ref name, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + public Region Region + { + get => region; + set => SetPropertyValue(nameof(Region), ref region, value); + } + + [Size(SizeAttribute.Unlimited)] + public string Address + { + get => address; + set => SetPropertyValue(nameof(Address), ref address, value); + } + + [RuleRequiredField(DefaultContexts.Save)] + public Point Point + { + get => point; + set => SetPropertyValue(nameof(Point), ref point, value); + } + } +} \ No newline at end of file diff --git a/DurnyklyYol.Module/Controllers/GoodsController.Designer.cs b/DurnyklyYol.Module/Controllers/GoodsController.Designer.cs new file mode 100644 index 0000000..2c91341 --- /dev/null +++ b/DurnyklyYol.Module/Controllers/GoodsController.Designer.cs @@ -0,0 +1,36 @@ +namespace DurnyklyYol.Module.Controllers +{ + partial class GoodsController + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/DurnyklyYol.Module/Controllers/GoodsController.cs b/DurnyklyYol.Module/Controllers/GoodsController.cs new file mode 100644 index 0000000..c402e93 --- /dev/null +++ b/DurnyklyYol.Module/Controllers/GoodsController.cs @@ -0,0 +1,53 @@ +using DevExpress.Data.Filtering; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.Actions; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Layout; +using DevExpress.ExpressApp.Model.NodeGenerators; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.SystemModule; +using DevExpress.ExpressApp.Templates; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Utils; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.Validation; +using DurnyklyYol.Module.BusinessObjects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace DurnyklyYol.Module.Controllers +{ + // For more typical usage scenarios, be sure to check out https://documentation.devexpress.com/eXpressAppFramework/clsDevExpressExpressAppViewControllertopic.aspx. + public partial class GoodsController : ObjectViewController + { + public GoodsController() + { + InitializeComponent(); + // Target required Views (via the TargetXXX properties) and create their Actions. + } + protected override void OnActivated() + { + base.OnActivated(); + + if (View is ListView listView) + { + if (IsCurrentUserInRoleOperator.IsCurrentUserInRole(GlobalConstants.ClientRoleName)) + { + listView.CollectionSource.Criteria["Filter1"] = CriteriaOperator.Parse("Client.Oid=CurrentUserId()"); + } + } + + //var gridListEditor = View.Editor as DevExpress.ExpressApp.Win.Editors.GridListEditor; + //if (gridListEditor != null) + //{ + // var gridView = gridListEditor.GridView; + // gridView.Columns["Country"].GroupIndex = 0; + // gridView.Columns["City"].GroupIndex = 1; + // gridView.Columns["Company"].GroupIndex = 2; + //} + } + + } +} diff --git a/DurnyklyYol.Module/Controllers/ReadMe.txt b/DurnyklyYol.Module/Controllers/ReadMe.txt new file mode 100644 index 0000000..9389414 --- /dev/null +++ b/DurnyklyYol.Module/Controllers/ReadMe.txt @@ -0,0 +1,25 @@ +Folder Description + +The "Controllers" project folder is intended for storing platform-agnostic Controller classes +that can change the default XAF application flow and add new features. + + +Relevant Documentation + +Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/112623 + +Implement Custom Controllers +https://docs.devexpress.com/eXpressAppFramework/112621 + +Define the Scope of Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/113103 + +Ways to Show a View +https://docs.devexpress.com/eXpressAppFramework/112803 + +Ways to Implement Business Logic +https://docs.devexpress.com/eXpressAppFramework/113710 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Module/DatabaseUpdate/ReadMe.txt b/DurnyklyYol.Module/DatabaseUpdate/ReadMe.txt new file mode 100644 index 0000000..436b186 --- /dev/null +++ b/DurnyklyYol.Module/DatabaseUpdate/ReadMe.txt @@ -0,0 +1,23 @@ +Folder Description + +The "DatabaseUpdate" project folder is intended for storing code that supplies +initial data (default User objects, etc) and handles a database update when the +application version changes. + + +Relevant Documentation + +Supply Initial Data (XPO) +https://docs.devexpress.com/eXpressAppFramework/112788 + +ModuleUpdater Class +https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Updating.ModuleUpdater + +Application Update +https://docs.devexpress.com/eXpressAppFramework/113239 + +How to: Update the Database Structure after the Persistent Class or Property Was Renamed or Removed (XPO) +https://docs.devexpress.com/eXpressAppFramework/113254 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Module/DatabaseUpdate/Updater.cs b/DurnyklyYol.Module/DatabaseUpdate/Updater.cs new file mode 100644 index 0000000..9a7c3a3 --- /dev/null +++ b/DurnyklyYol.Module/DatabaseUpdate/Updater.cs @@ -0,0 +1,116 @@ +using DevExpress.ExpressApp; +using DevExpress.Data.Filtering; +using DevExpress.Persistent.Base; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.SystemModule; +using DevExpress.ExpressApp.Security.Strategy; +using DevExpress.Xpo; +using DevExpress.ExpressApp.Xpo; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DurnyklyYol.Module.BusinessObjects; +using Microsoft.Extensions.DependencyInjection; + +namespace DurnyklyYol.Module.DatabaseUpdate; + +// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Updating.ModuleUpdater +public class Updater : ModuleUpdater { + public Updater(IObjectSpace objectSpace, Version currentDBVersion) : + base(objectSpace, currentDBVersion) { + } + public override void UpdateDatabaseAfterUpdateSchema() { + base.UpdateDatabaseAfterUpdateSchema(); + //string name = "MyName"; + //DomainObject1 theObject = ObjectSpace.FirstOrDefault(u => u.Name == name); + //if(theObject == null) { + // theObject = ObjectSpace.CreateObject(); + // theObject.Name = name; + //} + + + + // The code below creates users and roles for testing purposes only. + // In production code, you can create users and assign roles to them automatically, as described in the following help topic: + // https://docs.devexpress.com/eXpressAppFramework/119064/data-security-and-safety/security-system/authentication +#if !RELEASE + // If a role doesn't exist in the database, create this role + var defaultRole = CreateClientRole(); + var adminRole = CreateAdminRole(); + + ObjectSpace.CommitChanges(); //This line persists created object(s). + + UserManager userManager = ObjectSpace.ServiceProvider.GetRequiredService(); + // If a user named 'User' doesn't exist in the database, create this user + if(userManager.FindUserByName(ObjectSpace, "User") == null) { + // Set a password if the standard authentication type is used + string EmptyPassword = ""; + _ = userManager.CreateUser(ObjectSpace, "User", EmptyPassword, (user) => { + // Add the Users role to the user + user.Roles.Add(defaultRole); + }); + } + + // If a user named 'Admin' doesn't exist in the database, create this user + if(userManager.FindUserByName(ObjectSpace, "Admin") == null) { + // Set a password if the standard authentication type is used + string EmptyPassword = ""; + _ = userManager.CreateUser(ObjectSpace, "Admin", EmptyPassword, (user) => { + // Add the Administrators role to the user + user.Roles.Add(adminRole); + }); + } + + ObjectSpace.CommitChanges(); //This line persists created object(s). +#endif + } + public override void UpdateDatabaseBeforeUpdateSchema() { + base.UpdateDatabaseBeforeUpdateSchema(); + //if(CurrentDBVersion < new Version("1.1.0.0") && CurrentDBVersion > new Version("0.0.0.0")) { + // RenameColumn("DomainObject1Table", "OldColumnName", "NewColumnName"); + //} + } + private PermissionPolicyRole CreateAdminRole() { + PermissionPolicyRole adminRole = ObjectSpace.FirstOrDefault(r => r.Name == GlobalConstants.AdminRoleName); + if(adminRole == null) { + adminRole = ObjectSpace.CreateObject(); + adminRole.Name = GlobalConstants.AdminRoleName; + adminRole.IsAdministrative = true; + } + return adminRole; + } + private PermissionPolicyRole CreateClientRole() { + PermissionPolicyRole clRole = ObjectSpace.FirstOrDefault(role => role.Name == GlobalConstants.ClientRoleName); + if(clRole == null) { + clRole = ObjectSpace.CreateObject(); + clRole.Name = GlobalConstants.ClientRoleName; + + clRole.AddObjectPermissionFromLambda(SecurityOperations.Read, cm => cm.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow); + clRole.AddObjectPermissionFromLambda(SecurityOperations.Read, cm => cm.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow); + + clRole.AddObjectPermissionFromLambda(SecurityOperations.Read, cm => cm.Client.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow); + + clRole.AddNavigationPermission(@"Application/NavigationItems/Items/Default/Items/MyDetails", SecurityPermissionState.Allow); + clRole.AddNavigationPermission(@"Application/NavigationItems/Items/Clients & Goods/Items/Goods", SecurityPermissionState.Allow); + clRole.AddMemberPermissionFromLambda(SecurityOperations.Write, "ChangePasswordOnFirstLogon", cm => cm.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow); + clRole.AddMemberPermissionFromLambda(SecurityOperations.Write, "StoredPassword", cm => cm.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow); + clRole.AddTypePermissionsRecursively(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + clRole.AddTypePermission(SecurityOperations.Read, SecurityPermissionState.Allow); + + + //clRole.AddObjectPermission(SecurityOperations.ReadWriteAccess, "UserId = ToStr(CurrentUserId())", SecurityPermissionState.Deny); + //clRole.AddObjectPermission(SecurityOperations.ReadWriteAccess, "Owner.UserId = ToStr(CurrentUserId())", SecurityPermissionState.Deny); + //clRole.AddTypePermissionsRecursively(SecurityOperations.Create, SecurityPermissionState.Allow); + // clRole.AddTypePermissionsRecursively(SecurityOperations.Create, SecurityPermissionState.Allow); + } + return clRole; + } +} diff --git a/DurnyklyYol.Module/DurnyklyYol.Module.csproj b/DurnyklyYol.Module/DurnyklyYol.Module.csproj new file mode 100644 index 0000000..fecddeb --- /dev/null +++ b/DurnyklyYol.Module/DurnyklyYol.Module.csproj @@ -0,0 +1,51 @@ + + + net8.0 + false + false + 1.0.* + 1.0.0.0 + Debug;Release;EasyTest + enable + + + + + + + + Model.DesignedDiffs.xafml + + + + + + + + + + + + + + + + + + + + + + + + + + + + Model.DesignedDiffs.xafml + + + Model.DesignedDiffs.xafml + + + \ No newline at end of file diff --git a/DurnyklyYol.Module/GlobalConstants.cs b/DurnyklyYol.Module/GlobalConstants.cs new file mode 100644 index 0000000..f9d81aa --- /dev/null +++ b/DurnyklyYol.Module/GlobalConstants.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DurnyklyYol.Module +{ + public static class GlobalConstants + { + public const string ClientRoleName = "Client"; + public const string AdminRoleName = "Administrators"; + } +} diff --git a/DurnyklyYol.Module/Images/ReadMe.txt b/DurnyklyYol.Module/Images/ReadMe.txt new file mode 100644 index 0000000..230befc --- /dev/null +++ b/DurnyklyYol.Module/Images/ReadMe.txt @@ -0,0 +1,12 @@ +Folder Description + +The "Images" project folder is intended for storing custom image files. + + +Relevant Documentation + +Add and Override Images +https://docs.devexpress.com/eXpressAppFramework/112792 + +Assign a Custom Image +https://docs.devexpress.com/eXpressAppFramework/112744 diff --git a/DurnyklyYol.Module/Model.DesignedDiffs.Localization.tk-TM.xafml b/DurnyklyYol.Module/Model.DesignedDiffs.Localization.tk-TM.xafml new file mode 100644 index 0000000..9824d1a --- /dev/null +++ b/DurnyklyYol.Module/Model.DesignedDiffs.Localization.tk-TM.xafml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Module/Model.DesignedDiffs.xafml b/DurnyklyYol.Module/Model.DesignedDiffs.xafml new file mode 100644 index 0000000..890344b --- /dev/null +++ b/DurnyklyYol.Module/Model.DesignedDiffs.xafml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Module/Module.cs b/DurnyklyYol.Module/Module.cs new file mode 100644 index 0000000..3fbcbf3 --- /dev/null +++ b/DurnyklyYol.Module/Module.cs @@ -0,0 +1,53 @@ +using System.ComponentModel; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DevExpress.ExpressApp.Model; +using DevExpress.ExpressApp.Actions; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Model.Core; +using DevExpress.ExpressApp.Model.DomainLogics; +using DevExpress.ExpressApp.Model.NodeGenerators; +using DevExpress.Xpo; +using DevExpress.ExpressApp.Xpo; + +namespace DurnyklyYol.Module; + +// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase. +public sealed class DurnyklyYolModule : ModuleBase { + public DurnyklyYolModule() { + // + // DurnyklyYolModule + // + AdditionalExportedTypes.Add(typeof(OidGenerator)); + AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.ModelDifference)); + AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.ModelDifferenceAspect)); + AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.BaseObject)); + AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.FileData)); + AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.FileAttachmentBase)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.SystemModule.SystemModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Security.SecurityModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Objects.BusinessClassLibraryCustomizationModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.ConditionalAppearance.ConditionalAppearanceModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.PivotGrid.PivotGridModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.ReportsV2.ReportsModuleV2)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Validation.ValidationModule)); + RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.ViewVariantsModule.ViewVariantsModule)); + RequiredModuleTypes.Add(typeof(FileSystemData.FileSystemDataModule)); + } + public override IEnumerable GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) { + ModuleUpdater updater = new DatabaseUpdate.Updater(objectSpace, versionFromDB); + return new ModuleUpdater[] { updater }; + } + public override void Setup(XafApplication application) { + base.Setup(application); + // Manage various aspects of the application UI and behavior at the module level. + } + public override void CustomizeTypesInfo(ITypesInfo typesInfo) { + base.CustomizeTypesInfo(typesInfo); + CalculatedPersistentAliasHelper.CustomizeTypesInfo(typesInfo); + } +} diff --git a/DurnyklyYol.Module/ReadMe.txt b/DurnyklyYol.Module/ReadMe.txt new file mode 100644 index 0000000..087d288 --- /dev/null +++ b/DurnyklyYol.Module/ReadMe.txt @@ -0,0 +1,32 @@ +Project Description + +This project implements a platform-agnostic Module. UI-independent application +elements can be implemented here (Business Objects, Controllers, etc.). The root project +folder contains the Module.cs(vb) file with the class that inherits ModuleBase. +This class allows you to view and customize Module components: +referenced modules, Controllers and business classes. Additionally, the root folder +contains Application Model difference files (XAFML files) that keep application +settings specific for the current Module. Difference files can be customized in code +or in the Model Editor. + + +Relevant Documentation + +Application Solution Components +https://docs.devexpress.com/eXpressAppFramework/112569 + +XAF Community Extensions +https://www.devexpress.com/products/net/application_framework/#extensions + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 + +ModuleBase Class +https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase + + +Application Model +https://docs.devexpress.com/eXpressAppFramework/112579 + +Model Editor +https://docs.devexpress.com/eXpressAppFramework/112582 \ No newline at end of file diff --git a/DurnyklyYol.Module/UnusableNodes.xml b/DurnyklyYol.Module/UnusableNodes.xml new file mode 100644 index 0000000..5ba88ae --- /dev/null +++ b/DurnyklyYol.Module/UnusableNodes.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Module/UnusableNodes_tk-TM.xml b/DurnyklyYol.Module/UnusableNodes_tk-TM.xml new file mode 100644 index 0000000..483c5a4 --- /dev/null +++ b/DurnyklyYol.Module/UnusableNodes_tk-TM.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Module/Welcome.html b/DurnyklyYol.Module/Welcome.html new file mode 100644 index 0000000..28cd8d4 --- /dev/null +++ b/DurnyklyYol.Module/Welcome.html @@ -0,0 +1,192 @@ + + + + + Your XAF application + + + + +
+ + + + + +
+
+

The XAF Solution Wizard has successfully generated the solution according to your settings.

+
    +
  • The DurnyklyYol.Module project keeps your platform-agnostic code. + Add persistent objects code to the Business Objects folder of this project and XAF will automatically create a UI to create, read, update and delete these persistent objects.
  • +
  • The DurnyklyYol.Win project is a startup project for the desktop application.
  • +
  • To specify the database used by your application, modify the ConnectionString attribute in the configuration file located in the startup project.
  • +
  • To login, enter the "Admin" user name with empty password.
  • +
+
+
+ + + + + +
+
+ + + diff --git a/DurnyklyYol.Win/App.config b/DurnyklyYol.Win/App.config new file mode 100644 index 0000000..2e7968d --- /dev/null +++ b/DurnyklyYol.Win/App.config @@ -0,0 +1,24 @@ + + + + +
+ + + + + + System + + + + + + + + + + + + + diff --git a/DurnyklyYol.Win/Controllers/ReadMe.txt b/DurnyklyYol.Win/Controllers/ReadMe.txt new file mode 100644 index 0000000..a2c3f7a --- /dev/null +++ b/DurnyklyYol.Win/Controllers/ReadMe.txt @@ -0,0 +1,25 @@ +Folder Description + +The "Controllers" project folder is intended for storing WinForms-specific Controller classes +that can change the default XAF application flow and add new features. + + +Relevant Documentation + +Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/112623 + +Implement Custom Controllers +https://docs.devexpress.com/eXpressAppFramework/112621 + +Define the Scope of Controllers and Actions +https://docs.devexpress.com/eXpressAppFramework/113103 + +Ways to Show a View +https://docs.devexpress.com/eXpressAppFramework/112803 + +Ways to Implement Business Logic +https://docs.devexpress.com/eXpressAppFramework/113710 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Win/DurnyklyYol.Win.csproj b/DurnyklyYol.Win/DurnyklyYol.Win.csproj new file mode 100644 index 0000000..6a76100 --- /dev/null +++ b/DurnyklyYol.Win/DurnyklyYol.Win.csproj @@ -0,0 +1,50 @@ + + + WinExe + net8.0-windows + false + true + ExpressApp.ico + false + 1.0.* + 1.0.0.0 + Debug;Release;EasyTest + enable + + + + + + + + + + + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DurnyklyYol.Win/Editors/ReadMe.txt b/DurnyklyYol.Win/Editors/ReadMe.txt new file mode 100644 index 0000000..00ff025 --- /dev/null +++ b/DurnyklyYol.Win/Editors/ReadMe.txt @@ -0,0 +1,46 @@ +Folder Description + +This project folder is intended for storing custom WinForms List Editors, +Property Editors and View Items. + + +Relevant Documentation + +Using a Custom Control that is not Integrated by Default +https://docs.devexpress.com/eXpressAppFramework/113610 + +Ways to Access UI Elements and Their Controls +https://docs.devexpress.com/eXpressAppFramework/120092 + +Views +https://docs.devexpress.com/eXpressAppFramework/112611 + +List Editors +https://docs.devexpress.com/eXpressAppFramework/113189 + +Implement Custom Property Editors +https://docs.devexpress.com/eXpressAppFramework/113097 + +View Items +https://docs.devexpress.com/eXpressAppFramework/112612 + +How to: Implement a Custom WinForms List Editor +https://docs.devexpress.com/eXpressAppFramework/112659 + +How to: Support a Context Menu for a Custom WinForms List Editor +https://docs.devexpress.com/eXpressAppFramework/112660 + +How to: Implement a Property Editor (in WinForms Applications) +https://docs.devexpress.com/eXpressAppFramework/112679 + +How to: Implement a Property Editor for Specific Data Management (in WinForms Applications) +https://docs.devexpress.com/eXpressAppFramework/113101 + +How to: Extend Built-in Property Editor's Functionality +https://docs.devexpress.com/eXpressAppFramework/113104 + +How to: Implement a View Item +https://docs.devexpress.com/eXpressAppFramework/112641 + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 diff --git a/DurnyklyYol.Win/ExpressApp.ico b/DurnyklyYol.Win/ExpressApp.ico new file mode 100644 index 0000000000000000000000000000000000000000..e6810fc4f94dccd83ec54db6e9ef51a4950f6638 GIT binary patch literal 113407 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 literal 0 HcmV?d00001 diff --git a/DurnyklyYol.Win/Images/ExpressAppLogo.png b/DurnyklyYol.Win/Images/ExpressAppLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6eaada75b3745cfe120bd6c4a9b1fd9e3058fc GIT binary patch literal 7486 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%0D@};7Gcq+eA2G? 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 IM6N<$g0{$L^Z)<= literal 0 HcmV?d00001 diff --git a/DurnyklyYol.Win/Images/Logo.svg b/DurnyklyYol.Win/Images/Logo.svg new file mode 100644 index 0000000..5e22b7e --- /dev/null +++ b/DurnyklyYol.Win/Images/Logo.svg @@ -0,0 +1,16 @@ + + + + diff --git a/DurnyklyYol.Win/Images/ReadMe.txt b/DurnyklyYol.Win/Images/ReadMe.txt new file mode 100644 index 0000000..230befc --- /dev/null +++ b/DurnyklyYol.Win/Images/ReadMe.txt @@ -0,0 +1,12 @@ +Folder Description + +The "Images" project folder is intended for storing custom image files. + + +Relevant Documentation + +Add and Override Images +https://docs.devexpress.com/eXpressAppFramework/112792 + +Assign a Custom Image +https://docs.devexpress.com/eXpressAppFramework/112744 diff --git a/DurnyklyYol.Win/Model.xafml b/DurnyklyYol.Win/Model.xafml new file mode 100644 index 0000000..594847b --- /dev/null +++ b/DurnyklyYol.Win/Model.xafml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DurnyklyYol.Win/Program.cs b/DurnyklyYol.Win/Program.cs new file mode 100644 index 0000000..1fcacc3 --- /dev/null +++ b/DurnyklyYol.Win/Program.cs @@ -0,0 +1,51 @@ +using System.Configuration; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.ApplicationBuilder; +using DevExpress.ExpressApp.Win.ApplicationBuilder; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Win; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.XtraEditors; +using System.Net; +using System.Net.Http.Json; +using System.Net.Http.Headers; +using System.Net.Http; +using DevExpress.ExpressApp.Security.ClientServer; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; + +namespace DurnyklyYol.Win; + +static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + public static int Main(string[] args) { + DevExpress.ExpressApp.FrameworkSettings.DefaultSettingsCompatibilityMode = DevExpress.ExpressApp.FrameworkSettingsCompatibilityMode.Latest; + DevExpress.ExpressApp.Security.SecurityStrategy.AutoAssociationReferencePropertyMode = DevExpress.ExpressApp.Security.ReferenceWithoutAssociationPermissionsMode.AllMembers; +#if EASYTEST + DevExpress.ExpressApp.Win.EasyTest.EasyTestRemotingRegistration.Register(); +#endif + WindowsFormsSettings.LoadApplicationSettings(); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + DevExpress.Utils.ToolTipController.DefaultController.ToolTipType = DevExpress.Utils.ToolTipType.SuperTip; + if(Tracing.GetFileLocationFromSettings() == DevExpress.Persistent.Base.FileLocation.CurrentUserApplicationDataFolder) { + Tracing.LocalUserAppDataPath = Application.LocalUserAppDataPath; + } + Tracing.Initialize(); + + var winApplication = ApplicationBuilder.BuildApplication(); + + try { + winApplication.Setup(); + winApplication.Start(); + } + catch(Exception e) { + winApplication.StopSplash(); + winApplication.HandleException(e); + } + return 0; + } +} diff --git a/DurnyklyYol.Win/ReadMe.txt b/DurnyklyYol.Win/ReadMe.txt new file mode 100644 index 0000000..cebe462 --- /dev/null +++ b/DurnyklyYol.Win/ReadMe.txt @@ -0,0 +1,33 @@ +Project Description + +This project implements a WinForms application. The root project folder +contains the WinApplication.cs file with the class that inherits WinApplication. +This class allows you to view and customize Module components: +referenced modules, Controllers and business classes. Additionally, the root folder +contains Application Model difference files (XAFML files) that keep application +settings specific for the current Module. Difference files can be customized in code +or in the Model Editor. + + +Relevant Documentation + +Application Solution Components +https://docs.devexpress.com/eXpressAppFramework/112569 + +XAF Community Extensions +https://www.devexpress.com/products/net/application_framework/#extensions + +Debugging, Unit and Functional Testing +https://docs.devexpress.com/eXpressAppFramework/112572 + +WinApplication Class +https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Win.WinApplication + +XafApplication Class +https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.XafApplication + +Application Model +https://docs.devexpress.com/eXpressAppFramework/112579 + +Model Editor +https://docs.devexpress.com/eXpressAppFramework/112582 \ No newline at end of file diff --git a/DurnyklyYol.Win/Startup.cs b/DurnyklyYol.Win/Startup.cs new file mode 100644 index 0000000..f633d0b --- /dev/null +++ b/DurnyklyYol.Win/Startup.cs @@ -0,0 +1,77 @@ +using System.Configuration; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.ApplicationBuilder; +using DevExpress.ExpressApp.Win.ApplicationBuilder; +using DevExpress.ExpressApp.Security; +using DevExpress.ExpressApp.Win; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.XtraEditors; +using System.Net; +using System.Net.Http.Json; +using System.Net.Http.Headers; +using System.Net.Http; +using DevExpress.ExpressApp.Security.ClientServer; +using DevExpress.Persistent.BaseImpl.PermissionPolicy; +using DevExpress.ExpressApp.Design; + +namespace DurnyklyYol.Win; + +public class ApplicationBuilder : IDesignTimeApplicationFactory { + public static WinApplication BuildApplication() { + var builder = WinApplication.CreateBuilder(); + // Register custom services for Dependency Injection. For more information, refer to the following topic: https://docs.devexpress.com/eXpressAppFramework/404430/ + // builder.Services.AddScoped(); + // Register 3rd-party IoC containers (like Autofac, Dryloc, etc.) + // builder.UseServiceProviderFactory(new DryIocServiceProviderFactory()); + // builder.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + + builder.UseApplication(); + builder.Modules + .AddConditionalAppearance() + .AddFileAttachments() + .AddPivotGrid() + .AddReports(options => { + options.EnableInplaceReports = true; + options.ReportDataType = typeof(DevExpress.Persistent.BaseImpl.ReportDataV2); + options.ReportStoreMode = DevExpress.ExpressApp.ReportsV2.ReportStoreModes.XML; + }) + .AddValidation(options => { + options.AllowValidationDetailsAccess = false; + }) + .AddViewVariants() + .Add() + .Add(); + builder.ObjectSpaceProviders + .AddNonPersistent(); + builder.Security + .UseMiddleTierMode(options => { +#if DEBUG + options.WaitForMiddleTierServerReady(); +#endif + options.BaseAddress = new Uri("http://localhost:5000/"); + options.Events.OnHttpClientCreated = client => client.DefaultRequestHeaders.Add("Accept", "application/json"); + options.Events.OnCustomAuthenticate = (sender, security, args) => { + args.Handled = true; + HttpResponseMessage msg = args.HttpClient.PostAsJsonAsync("api/Authentication/Authenticate", (AuthenticationStandardLogonParameters)args.LogonParameters).GetAwaiter().GetResult(); + string token = (string)msg.Content.ReadFromJsonAsync(typeof(string)).GetAwaiter().GetResult(); + if(msg.StatusCode == HttpStatusCode.Unauthorized) { + XafExceptions.Authentication.ThrowAuthenticationFailedFromResponse(token); + } + msg.EnsureSuccessStatusCode(); + args.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); + }; + }) + .AddPasswordAuthentication(); + builder.AddBuildStep(application => { + }); + var winApplication = builder.Build(); + return winApplication; + } + + XafApplication IDesignTimeApplicationFactory.Create() { + DevExpress.ExpressApp.Security.ClientServer.MiddleTierClientSecurity.DesignModeUserType = typeof(DurnyklyYol.Module.BusinessObjects.ApplicationUser); + DevExpress.ExpressApp.Security.ClientServer.MiddleTierClientSecurity.DesignModeRoleType = typeof(DevExpress.Persistent.BaseImpl.PermissionPolicy.PermissionPolicyRole); + return BuildApplication(); + } +} diff --git a/DurnyklyYol.Win/WinApplication.cs b/DurnyklyYol.Win/WinApplication.cs new file mode 100644 index 0000000..8e187c7 --- /dev/null +++ b/DurnyklyYol.Win/WinApplication.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.ApplicationBuilder; +using DevExpress.ExpressApp.Win; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Win.Utils; +using DevExpress.ExpressApp.Xpo; + +namespace DurnyklyYol.Win; + +// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Win.WinApplication._members +public class DurnyklyYolWindowsFormsApplication : WinApplication { + public DurnyklyYolWindowsFormsApplication() { + SplashScreen = new DXSplashScreen(typeof(XafSplashScreen), new DefaultOverlayFormOptions()); + ApplicationName = "DurnyklyYol"; + CheckCompatibilityType = DevExpress.ExpressApp.CheckCompatibilityType.DatabaseSchema; + UseOldTemplates = false; + DatabaseVersionMismatch += DurnyklyYolWindowsFormsApplication_DatabaseVersionMismatch; + CustomizeLanguagesList += DurnyklyYolWindowsFormsApplication_CustomizeLanguagesList; + } + private void DurnyklyYolWindowsFormsApplication_CustomizeLanguagesList(object sender, CustomizeLanguagesListEventArgs e) { + string userLanguageName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + if(userLanguageName != "en-US" && e.Languages.IndexOf(userLanguageName) == -1) { + e.Languages.Add(userLanguageName); + } + } + private void DurnyklyYolWindowsFormsApplication_DatabaseVersionMismatch(object sender, DevExpress.ExpressApp.DatabaseVersionMismatchEventArgs e) { + string message = "Application cannot connect to the specified database."; + + CompatibilityDatabaseIsOldError isOldError = e.CompatibilityError as CompatibilityDatabaseIsOldError; + if(isOldError != null && isOldError.Module != null) { + message = "The client application cannot connect to the Middle Tier Application Server and its database. " + + "To avoid this error, ensure that both the client and the server have the same modules set. Problematic module: " + isOldError.Module.Name + + ". For more information, see https://docs.devexpress.com/eXpressAppFramework/113439/concepts/security-system/middle-tier-security-wcf-service#troubleshooting"; + } + if(e.CompatibilityError == null) { + message = "You probably tried to update the database in Middle Tier Security mode from the client side. " + + "In this mode, the server application updates the database automatically. " + + "To disable the automatic database update, set the XafApplication.DatabaseUpdateMode property to the DatabaseUpdateMode.Never value in the client application."; + } + throw new InvalidOperationException(message); + } +} diff --git a/DurnyklyYol.Win/WinModule.cs b/DurnyklyYol.Win/WinModule.cs new file mode 100644 index 0000000..7439e40 --- /dev/null +++ b/DurnyklyYol.Win/WinModule.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using DevExpress.ExpressApp; +using DevExpress.ExpressApp.DC; +using DevExpress.ExpressApp.Model; +using DevExpress.ExpressApp.Editors; +using DevExpress.ExpressApp.Actions; +using DevExpress.ExpressApp.Updating; +using DevExpress.ExpressApp.Model.Core; +using DevExpress.ExpressApp.Model.DomainLogics; +using DevExpress.ExpressApp.Model.NodeGenerators; +using DevExpress.Persistent.BaseImpl; + +namespace DurnyklyYol.Win; + +[ToolboxItemFilter("Xaf.Platform.Win")] +// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase. +public sealed class DurnyklyYolWinModule : ModuleBase { + //private void Application_CreateCustomModelDifferenceStore(object sender, CreateCustomModelDifferenceStoreEventArgs e) { + // e.Store = new ModelDifferenceDbStore((XafApplication)sender, typeof(ModelDifference), true, "Win"); + // e.Handled = true; + //} + private void Application_CreateCustomUserModelDifferenceStore(object sender, CreateCustomModelDifferenceStoreEventArgs e) { + e.Store = new ModelDifferenceDbStore((XafApplication)sender, typeof(ModelDifference), false, "Win"); + e.Handled = true; + } + public DurnyklyYolWinModule() { + DevExpress.ExpressApp.Editors.FormattingProvider.UseMaskSettings = true; + } + public override IEnumerable GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) { + return ModuleUpdater.EmptyModuleUpdaters; + } + public override void Setup(XafApplication application) { + base.Setup(application); + //application.CreateCustomModelDifferenceStore += Application_CreateCustomModelDifferenceStore; + application.CreateCustomUserModelDifferenceStore += Application_CreateCustomUserModelDifferenceStore; + } +} diff --git a/DurnyklyYol.Win/XafSplashScreen.Designer.cs b/DurnyklyYol.Win/XafSplashScreen.Designer.cs new file mode 100644 index 0000000..a399afc --- /dev/null +++ b/DurnyklyYol.Win/XafSplashScreen.Designer.cs @@ -0,0 +1,181 @@ +namespace DurnyklyYol.Win { + partial class XafSplashScreen + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(XafSplashScreen)); + this.progressBarControl = new DevExpress.XtraEditors.MarqueeProgressBarControl(); + this.labelCopyright = new DevExpress.XtraEditors.LabelControl(); + this.labelStatus = new DevExpress.XtraEditors.LabelControl(); + this.peImage = new DevExpress.XtraEditors.PictureEdit(); + this.peLogo = new DevExpress.XtraEditors.PictureEdit(); + this.pcApplicationName = new DevExpress.XtraEditors.PanelControl(); + this.labelSubtitle = new DevExpress.XtraEditors.LabelControl(); + this.labelApplicationName = new DevExpress.XtraEditors.LabelControl(); + ((System.ComponentModel.ISupportInitialize)(this.progressBarControl.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.peImage.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.peLogo.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcApplicationName)).BeginInit(); + this.pcApplicationName.SuspendLayout(); + this.SuspendLayout(); + // + // progressBarControl + // + this.progressBarControl.EditValue = 0; + this.progressBarControl.Location = new System.Drawing.Point(74, 271); + this.progressBarControl.Name = "progressBarControl"; + this.progressBarControl.Properties.Appearance.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(195)))), ((int)(((byte)(194)))), ((int)(((byte)(194))))); + this.progressBarControl.Properties.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.Simple; + this.progressBarControl.Properties.EndColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(114)))), ((int)(((byte)(0))))); + this.progressBarControl.Properties.LookAndFeel.SkinName = "Visual Studio 2013 Blue"; + this.progressBarControl.Properties.LookAndFeel.Style = DevExpress.LookAndFeel.LookAndFeelStyle.UltraFlat; + this.progressBarControl.Properties.LookAndFeel.UseDefaultLookAndFeel = false; + this.progressBarControl.Properties.ProgressViewStyle = DevExpress.XtraEditors.Controls.ProgressViewStyle.Solid; + this.progressBarControl.Properties.StartColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(144)))), ((int)(((byte)(0))))); + this.progressBarControl.Size = new System.Drawing.Size(350, 16); + this.progressBarControl.TabIndex = 5; + // + // labelCopyright + // + this.labelCopyright.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder; + this.labelCopyright.Location = new System.Drawing.Point(24, 324); + this.labelCopyright.Name = "labelCopyright"; + this.labelCopyright.Size = new System.Drawing.Size(47, 13); + this.labelCopyright.TabIndex = 6; + this.labelCopyright.Text = "Copyright"; + // + // labelStatus + // + this.labelStatus.Location = new System.Drawing.Point(75, 253); + this.labelStatus.Name = "labelStatus"; + this.labelStatus.Size = new System.Drawing.Size(50, 13); + this.labelStatus.TabIndex = 7; + this.labelStatus.Text = "Starting..."; + // + // peImage + // + this.peImage.EditValue = ((object)(resources.GetObject("peImage.EditValue"))); + this.peImage.Location = new System.Drawing.Point(12, 12); + this.peImage.Name = "peImage"; + this.peImage.Properties.AllowFocused = false; + this.peImage.Properties.Appearance.BackColor = System.Drawing.Color.Transparent; + this.peImage.Properties.Appearance.Options.UseBackColor = true; + this.peImage.Properties.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder; + this.peImage.Properties.ShowMenu = false; + this.peImage.Size = new System.Drawing.Size(426, 180); + this.peImage.TabIndex = 9; + this.peImage.Visible = false; + // + // peLogo + // + this.peLogo.EditValue = ((object)(resources.GetObject("peLogo.EditValue"))); + this.peLogo.Location = new System.Drawing.Point(400, 328); + this.peLogo.Name = "peLogo"; + this.peLogo.Properties.AllowFocused = false; + this.peLogo.Properties.Appearance.BackColor = System.Drawing.Color.Transparent; + this.peLogo.Properties.Appearance.Options.UseBackColor = true; + this.peLogo.Properties.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder; + this.peLogo.Properties.ShowMenu = false; + this.peLogo.Size = new System.Drawing.Size(70, 20); + this.peLogo.TabIndex = 8; + // + // pcApplicationName + // + this.pcApplicationName.Appearance.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(114)))), ((int)(((byte)(0))))); + this.pcApplicationName.Appearance.Options.UseBackColor = true; + this.pcApplicationName.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder; + this.pcApplicationName.Controls.Add(this.labelSubtitle); + this.pcApplicationName.Controls.Add(this.labelApplicationName); + this.pcApplicationName.Dock = System.Windows.Forms.DockStyle.Top; + this.pcApplicationName.Location = new System.Drawing.Point(1, 1); + this.pcApplicationName.LookAndFeel.UseDefaultLookAndFeel = false; + this.pcApplicationName.Name = "pcApplicationName"; + this.pcApplicationName.Size = new System.Drawing.Size(494, 220); + this.pcApplicationName.TabIndex = 10; + // + // labelSubtitle + // + this.labelSubtitle.Appearance.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelSubtitle.Appearance.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(216)))), ((int)(((byte)(188))))); + this.labelSubtitle.Appearance.Options.UseFont = true; + this.labelSubtitle.Appearance.Options.UseForeColor = true; + this.labelSubtitle.Location = new System.Drawing.Point(222, 131); + this.labelSubtitle.Name = "labelSubtitle"; + this.labelSubtitle.Size = new System.Drawing.Size(64, 25); + this.labelSubtitle.TabIndex = 1; + this.labelSubtitle.Text = "Subtitle"; + // + // labelApplicationName + // + this.labelApplicationName.Appearance.Font = new System.Drawing.Font("Segoe UI", 26.25F); + this.labelApplicationName.Appearance.ForeColor = System.Drawing.SystemColors.Window; + this.labelApplicationName.Appearance.Options.UseFont = true; + this.labelApplicationName.Appearance.Options.UseForeColor = true; + this.labelApplicationName.Location = new System.Drawing.Point(123, 84); + this.labelApplicationName.Name = "labelApplicationName"; + this.labelApplicationName.Size = new System.Drawing.Size(278, 47); + this.labelApplicationName.TabIndex = 0; + this.labelApplicationName.Text = "Application Name"; + // + // XafSplashScreen + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.White; + this.ClientSize = new System.Drawing.Size(496, 370); + this.Controls.Add(this.pcApplicationName); + this.Controls.Add(this.peImage); + this.Controls.Add(this.peLogo); + this.Controls.Add(this.labelStatus); + this.Controls.Add(this.labelCopyright); + this.Controls.Add(this.progressBarControl); + this.Name = "XafSplashScreen"; + this.Padding = new System.Windows.Forms.Padding(1); + this.Text = "Form1"; + ((System.ComponentModel.ISupportInitialize)(this.progressBarControl.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.peImage.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.peLogo.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcApplicationName)).EndInit(); + this.pcApplicationName.ResumeLayout(false); + this.pcApplicationName.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private DevExpress.XtraEditors.MarqueeProgressBarControl progressBarControl; + private DevExpress.XtraEditors.LabelControl labelCopyright; + private DevExpress.XtraEditors.LabelControl labelStatus; + private DevExpress.XtraEditors.PictureEdit peLogo; + private DevExpress.XtraEditors.PictureEdit peImage; + private DevExpress.XtraEditors.PanelControl pcApplicationName; + private DevExpress.XtraEditors.LabelControl labelSubtitle; + private DevExpress.XtraEditors.LabelControl labelApplicationName; + } +} diff --git a/DurnyklyYol.Win/XafSplashScreen.cs b/DurnyklyYol.Win/XafSplashScreen.cs new file mode 100644 index 0000000..491dde6 --- /dev/null +++ b/DurnyklyYol.Win/XafSplashScreen.cs @@ -0,0 +1,54 @@ +using System; +using System.Drawing; +using System.IO; +using System.Reflection; +using DevExpress.ExpressApp.Win.Utils; +using DevExpress.Skins; +using DevExpress.Utils.Drawing; +using DevExpress.Utils.Svg; +using DevExpress.XtraSplashScreen; + +namespace DurnyklyYol.Win { + public partial class XafSplashScreen : SplashScreen { + private void LoadBlankLogo() { + Assembly assembly = Assembly.GetExecutingAssembly(); + string blankLogoResourceName = assembly.GetName().Name + ".Images.Logo.svg"; + Stream svgStream = assembly.GetManifestResourceStream(blankLogoResourceName); + if(svgStream != null) { + svgStream.Position = 0; + peLogo.SvgImage = SvgImage.FromStream(svgStream); + } + } + protected override void DrawContent(GraphicsCache graphicsCache, Skin skin) { + Rectangle bounds = ClientRectangle; + bounds.Width--; bounds.Height--; + graphicsCache.Graphics.DrawRectangle(graphicsCache.GetPen(Color.FromArgb(255, 87, 87, 87), 1), bounds); + } + protected void UpdateLabelsPosition() { + labelApplicationName.CalcBestSize(); + int newLeft = (Width - labelApplicationName.Width) / 2; + labelApplicationName.Location = new Point(newLeft, labelApplicationName.Top); + labelSubtitle.CalcBestSize(); + newLeft = (Width - labelSubtitle.Width) / 2; + labelSubtitle.Location = new Point(newLeft, labelSubtitle.Top); + } + public XafSplashScreen() { + InitializeComponent(); + LoadBlankLogo(); + this.labelCopyright.Text = "Copyright © " + DateTime.Now.Year.ToString() + " Company Name" + System.Environment.NewLine + "All rights reserved."; + UpdateLabelsPosition(); + } + + #region Overrides + + public override void ProcessCommand(Enum cmd, object arg) { + base.ProcessCommand(cmd, arg); + if((UpdateSplashCommand)cmd == UpdateSplashCommand.Description) { + labelStatus.Text = (string)arg; + } + } + + #endregion + + } +} \ No newline at end of file diff --git a/DurnyklyYol.Win/XafSplashScreen.resx b/DurnyklyYol.Win/XafSplashScreen.resx new file mode 100644 index 0000000..18fc5b4 --- /dev/null +++ b/DurnyklyYol.Win/XafSplashScreen.resx @@ -0,0 +1,638 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + 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 + YII= + + + \ No newline at end of file diff --git a/DurnyklyYol.sln b/DurnyklyYol.sln new file mode 100644 index 0000000..8e05b66 --- /dev/null +++ b/DurnyklyYol.sln @@ -0,0 +1,60 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33530.505 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurnyklyYol.Module", "DurnyklyYol.Module\DurnyklyYol.Module.csproj", "{F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurnyklyYol.MiddleTier", "DurnyklyYol.MiddleTier\DurnyklyYol.MiddleTier.csproj", "{4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurnyklyYol.Win", "DurnyklyYol.Win\DurnyklyYol.Win.csproj", "{8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurnyklyYol.Blazor.Server", "DurnyklyYol.Blazor.Server\DurnyklyYol.Blazor.Server.csproj", "{35F479E5-270B-4E83-A2AA-5F18FF1806E9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileSystemData", "FileSystemData\FileSystemData.csproj", "{2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EasyTest|Any CPU = EasyTest|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.EasyTest|Any CPU.ActiveCfg = EasyTest|Any CPU + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.EasyTest|Any CPU.Build.0 = EasyTest|Any CPU + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3EDEF37-DE3A-4F59-A332-04EEBED24E8B}.Release|Any CPU.Build.0 = Release|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.EasyTest|Any CPU.ActiveCfg = EasyTest|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.EasyTest|Any CPU.Build.0 = EasyTest|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FAA61B0-0F66-4C83-9166-90D8B92C8D7D}.Release|Any CPU.Build.0 = Release|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.EasyTest|Any CPU.ActiveCfg = EasyTest|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.EasyTest|Any CPU.Build.0 = EasyTest|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BCCBE43-2B6A-4DF1-B50B-AD344B6613AA}.Release|Any CPU.Build.0 = Release|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.EasyTest|Any CPU.ActiveCfg = EasyTest|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.EasyTest|Any CPU.Build.0 = EasyTest|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35F479E5-270B-4E83-A2AA-5F18FF1806E9}.Release|Any CPU.Build.0 = Release|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.EasyTest|Any CPU.ActiveCfg = Debug|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.EasyTest|Any CPU.Build.0 = Debug|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D5F4BAF-09AB-436E-BFB6-040D3F2E8541}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {933566C9-9F21-4ED9-BA02-638D1BBEF969} + EndGlobalSection +EndGlobal diff --git a/FileSystemData/BusinessObjects/FileSystemLinkObject.cs b/FileSystemData/BusinessObjects/FileSystemLinkObject.cs new file mode 100644 index 0000000..1b46101 --- /dev/null +++ b/FileSystemData/BusinessObjects/FileSystemLinkObject.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using DevExpress.Xpo; +using DevExpress.ExpressApp; +using System.ComponentModel; +using DevExpress.Persistent.Base; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; + +namespace FileSystemData.BusinessObjects { + /// + /// This class enables you to add soft links to real files instead of saving their contents to the database. It is intended for use in Windows Forms applications only. + /// + [DefaultProperty(nameof(FileName))] + public class FileSystemLinkObject : BaseObject, IFileData, IEmptyCheckable, ISupportFullName { + public FileSystemLinkObject(Session session) : base(session) { } + #region IFileData Members + [Size(260), Custom("AllowEdit", "False")] + public string FileName { + get { return GetPropertyValue(nameof(FileName)); } + set { SetPropertyValue(nameof(FileName), value); } + } + void IFileData.Clear() { + Size = 0; + FileName = string.Empty; + } + //Dennis: Fires when uploading a file. + void IFileData.LoadFromStream(string fileName, Stream source) { + Size = (int)source.Length; + FileName = fileName; + } + //Dennis: Fires when saving or opening a file. + void IFileData.SaveToStream(Stream destination) { + try { + if (destination == null) + FileSystemDataModule.OpenFileWithDefaultProgram(FullName); + else + FileSystemDataModule.CopyFileToStream(FullName, destination); + } catch (Exception exc) { + throw new UserFriendlyException(exc); + } + } + [Persistent] + public int Size { + get { return GetPropertyValue(nameof(Size)); } + private set { SetPropertyValue(nameof(Size), value); } + } + #endregion + #region IEmptyCheckable Members + public bool IsEmpty { + get { return !File.Exists(FullName); } + } + #endregion + #region ISupportFullName Members + [Size(260), Custom("AllowEdit", "False")] + public string FullName { + get { return GetPropertyValue(nameof(FullName)); } + set { SetPropertyValue(nameof(FullName), value); } + } + #endregion + } +} \ No newline at end of file diff --git a/FileSystemData/BusinessObjects/FileSystemStoreObject.cs b/FileSystemData/BusinessObjects/FileSystemStoreObject.cs new file mode 100644 index 0000000..e5bf47e --- /dev/null +++ b/FileSystemData/BusinessObjects/FileSystemStoreObject.cs @@ -0,0 +1,162 @@ +using System; +using System.IO; +using DevExpress.Xpo; +using DevExpress.ExpressApp; +using System.ComponentModel; +using DevExpress.Persistent.Base; +using DevExpress.ExpressApp.Utils; +using DevExpress.Persistent.BaseImpl; +using DevExpress.Persistent.Validation; +using System.Linq; + +namespace FileSystemData.BusinessObjects { + /// + /// This class enables you to store uploaded files in a centralized file system location instead of the database. You can configure the file system store location via the static FileSystemDataModule.FileSystemStoreLocation property. + /// + [DefaultProperty(nameof(FileName))] + public class FileSystemStoreObject : BaseObject, IFileData, IEmptyCheckable { + private Stream tempSourceStream; + private string tempFileName = string.Empty; + private static object syncRoot = new object(); + public FileSystemStoreObject(Session session) : base(session) { } + public string RealFileName { + get { + if (!string.IsNullOrEmpty(FileName) && Oid != Guid.Empty) + return Path.Combine(FileSystemDataModule.FileSystemStoreLocation, string.Format("{0}-{1}", Oid, FileName)); + return null; + } + } + protected virtual void SaveFileToStore() { + if(!string.IsNullOrEmpty(RealFileName) && TempSourceStream != null) { + try { + using(Stream destination = File.Create(RealFileName)) { //T582918 + FileSystemDataModule.CopyStream(TempSourceStream, destination); + Size = (int)destination.Length; + } + } + catch(DirectoryNotFoundException exc) { + throw new UserFriendlyException(exc); + } + } + } + private void RemoveOldFileFromStore() { + //Dennis: We need to remove the old file from the store when saving the current object. + if (!string.IsNullOrEmpty(tempFileName) && tempFileName != RealFileName) {//B222892 + try { + File.Delete(tempFileName); + tempFileName = string.Empty; + } catch (DirectoryNotFoundException exc) { + throw new UserFriendlyException(exc); + } + } + } + protected override void OnSaving() { + base.OnSaving(); + Guard.ArgumentNotNullOrEmpty(FileSystemDataModule.FileSystemStoreLocation, "FileSystemStoreLocation"); + lock (syncRoot) { + if (!Directory.Exists(FileSystemDataModule.FileSystemStoreLocation)) + Directory.CreateDirectory(FileSystemDataModule.FileSystemStoreLocation); + } + SaveFileToStore(); + RemoveOldFileFromStore(); + } + protected override void OnDeleting() { + //Dennis: We need to remove the old file from the store. + Clear(); + base.OnDeleting(); + } + protected override void Invalidate(bool disposing) { + if (disposing && TempSourceStream != null) { + TempSourceStream.Close(); + TempSourceStream = null; + } + base.Invalidate(disposing); + } + #region IFileData Members + public void Clear() { + //Dennis: When clearing the file name property we need to save the name of the old file to remove it from the store in the future. You can also setup a separate service for that. + if (string.IsNullOrEmpty(tempFileName)) + tempFileName = RealFileName; + FileName = string.Empty; + Size = 0; + } + [Size(260)] + public string FileName { + get { return GetPropertyValue(nameof(FileName)); } + set { SetPropertyValue(nameof(FileName), value); } + } + [Browsable(false)] + public Stream TempSourceStream { + get { return tempSourceStream; } + set { + //Michael: The original Stream might be closed after a while (on the web too - T160753) + if (value == null) { + tempSourceStream = null; + } else { + if (value.Length > (long)int.MaxValue) throw new UserFriendlyException("File is too long"); + tempSourceStream = new MemoryStream((int)value.Length); + FileSystemDataModule.CopyStream(value, tempSourceStream); + tempSourceStream.Position = 0; + } + } + } + //Dennis: Fires when uploading a file. + void IFileData.LoadFromStream(string fileName,Stream source) { + if (source.Length > 524288) + { // 512 KB + throw new UserFriendlyException("The file size must be less than or equal to 512 KB."); + } + // Validate file type (only images) + string fileExtension = Path.GetExtension(fileName)?.ToLower(); + string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".bmp",".heif",".heic" }; // Add other image formats as needed + + if (!allowedExtensions.Contains(fileExtension)) + { + throw new UserFriendlyException("Only image files (JPG, JPEG, PNG, BMP, heif, heic) are allowed."); + } + + //Dennis: When assigning a new file we need to save the name of the old file to remove it from the store in the future. + if (fileName != FileName) {// updated, old code was: if (string.IsNullOrEmpty(tempFileName)) + tempFileName = RealFileName; + } + FileName = fileName; + TempSourceStream = source; + Size = (int)TempSourceStream.Length; + OnChanged();//T582918 + } + //Dennis: Fires when saving or opening a file. + void IFileData.SaveToStream(Stream destination) { + try { + if(!File.Exists(RealFileName)) { + return; + } + if (!string.IsNullOrEmpty(RealFileName)) { + if (destination == null) + FileSystemDataModule.OpenFileWithDefaultProgram(RealFileName); + else + FileSystemDataModule.CopyFileToStream(RealFileName, destination); + } + else if (TempSourceStream != null) + FileSystemDataModule.CopyStream(TempSourceStream, destination); + } catch (DirectoryNotFoundException exc) { + throw new UserFriendlyException(exc); + } catch (FileNotFoundException exc) { + throw new UserFriendlyException(exc); + } + } + [Persistent] + [RuleValueComparison("FileSizeValidation", DefaultContexts.Save, ValueComparisonType.LessThanOrEqual, 524288, // 512 KB + CustomMessageTemplate = "The file size must be less than or equal to 512 KB.")] + public int Size { + get { return GetPropertyValue(nameof(Size)); } + private set { SetPropertyValue(nameof(Size), value); } + } + #endregion + #region IEmptyCheckable Members + public bool IsEmpty { + //T153149 + get { return FileDataHelper.IsFileDataEmpty(this) || !(TempSourceStream!= null || File.Exists(RealFileName)); } + } + #endregion + } +} \ No newline at end of file diff --git a/FileSystemData/FileSystemData.csproj b/FileSystemData/FileSystemData.csproj new file mode 100644 index 0000000..57b8471 --- /dev/null +++ b/FileSystemData/FileSystemData.csproj @@ -0,0 +1,37 @@ + + + Library + net6 + FileSystemData + FileSystemData + false + false + + + TRACE;DEBUG + + + TRACE + + + TRACE;DEBUG;EASYTEST + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + \ No newline at end of file diff --git a/FileSystemData/Module.Designer.cs b/FileSystemData/Module.Designer.cs new file mode 100644 index 0000000..bba9f32 --- /dev/null +++ b/FileSystemData/Module.Designer.cs @@ -0,0 +1,31 @@ +namespace FileSystemData { + partial class FileSystemDataModule { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + + } + + #endregion + } +} diff --git a/FileSystemData/Module.cs b/FileSystemData/Module.cs new file mode 100644 index 0000000..e0e16e8 --- /dev/null +++ b/FileSystemData/Module.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.ComponentModel; +using DevExpress.ExpressApp; +using System.Collections.Generic; +using DevExpress.Persistent.Base; +using DevExpress.ExpressApp.Utils; +using DevExpress.Persistent.BaseImpl; + +namespace FileSystemData { + [Description("This module provides the FileSystemStoreObject and FileSystemLinkObject classes that enable you to store uploaded files in a file system instead of the database.")] + public sealed partial class FileSystemDataModule : ModuleBase { + public static int ReadBytesSize = 0x1000; + public static string FileSystemStoreLocation = String.Format("{0}FileData", PathHelper.GetApplicationFolder()); + public FileSystemDataModule() { + InitializeComponent(); + BaseObject.OidInitializationMode = OidInitializationMode.AfterConstruction; + } + public static void CopyFileToStream(string sourceFileName, Stream destination) { + if (string.IsNullOrEmpty(sourceFileName) || destination == null) return; + using (Stream source = File.OpenRead(sourceFileName)) + CopyStream(source, destination); + } + public static void OpenFileWithDefaultProgram(string sourceFileName) { + Guard.ArgumentNotNullOrEmpty(sourceFileName, "sourceFileName"); + + System.Diagnostics.Process process = new System.Diagnostics.Process(); + + process.StartInfo.UseShellExecute = true; + process.StartInfo.FileName = sourceFileName; + process.Start(); + } + public static void CopyStream(Stream source, Stream destination) { + if (source == null || destination == null) return; + byte[] buffer = new byte[ReadBytesSize]; + int read = 0; + while ((read = source.Read(buffer, 0, buffer.Length)) > 0) + destination.Write(buffer, 0, read); + } + } +} \ No newline at end of file diff --git a/FileSystemData/Module.resx b/FileSystemData/Module.resx new file mode 100644 index 0000000..7080a7d --- /dev/null +++ b/FileSystemData/Module.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file