diff --git a/.gitignore b/.gitignore index a9b29e0..833b486 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .vs/ bin/ -obj/ \ No newline at end of file +obj/ +pack.bat +push.bat +*.user \ No newline at end of file diff --git a/ExtCore.sln b/ExtCore.sln index 877b159..62e358a 100644 --- a/ExtCore.sln +++ b/ExtCore.sln @@ -1,10 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.10 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32825.248 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Infrastructure", "src\ExtCore.Infrastructure\ExtCore.Infrastructure.csproj", "{1A7B4E8E-9D6D-4DD2-862D-92A6BDDBC2C8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.WebApplication", "src\ExtCore.WebApplication\ExtCore.WebApplication.csproj", "{0F8F961C-2A13-47A2-9EFE-245FBA7A1C89}" @@ -15,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Mvc.Infrastructure" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data", "src\ExtCore.Data\ExtCore.Data.csproj", "{BD3ADB01-9621-4A50-9D0E-8D4CC88A79D8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Models.Abstractions", "src\ExtCore.Data.Models.Abstractions\ExtCore.Data.Models.Abstractions.csproj", "{B713F760-F0A8-4A47-8FC0-2F08E85D5AF9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Abstractions", "src\ExtCore.Data.Abstractions\ExtCore.Data.Abstractions.csproj", "{08204286-5843-46F5-B704-8F1183F7686E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.EntityFramework.PostgreSql", "src\ExtCore.Data.EntityFramework.PostgreSql\ExtCore.Data.EntityFramework.PostgreSql.csproj", "{A5B17E64-9C8B-40EB-A4FD-637BB1E1BD05}" @@ -33,6 +29,38 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Events", "Events", "{B0915C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Events", "src\ExtCore.Events\ExtCore.Events.csproj", "{3E5C83A3-2C66-4503-A3F8-542854D3D2EF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.EntityFramework", "src\ExtCore.Data.EntityFramework\ExtCore.Data.EntityFramework.csproj", "{77F8B77C-440C-48D9-80FA-192FF49283C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Entities.Abstractions", "src\ExtCore.Data.Entities.Abstractions\ExtCore.Data.Entities.Abstractions.csproj", "{A97967FC-B333-4C27-BAFE-4369C7695688}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data.EntityFramework", "Data.EntityFramework", "{A770A286-2B75-4BF7-8300-24CEC8D072AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.EntityFramework.MySql", "src\ExtCore.Data.EntityFramework.MySql\ExtCore.Data.EntityFramework.MySql.csproj", "{79A8C5FE-94FA-4D60-86C7-016EBF0386B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Dapper", "src\ExtCore.Data.Dapper\ExtCore.Data.Dapper.csproj", "{976AD81D-4F7D-4A0C-9A2F-F665566EA5A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Dapper.MySql", "src\ExtCore.Data.Dapper.MySql\ExtCore.Data.Dapper.MySql.csproj", "{F27A3AE9-718C-4539-B541-4F37D16E4463}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Dapper.PostgreSql", "src\ExtCore.Data.Dapper.PostgreSql\ExtCore.Data.Dapper.PostgreSql.csproj", "{7B7A5A6C-DB45-4056-B5C7-4286D16220DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Dapper.SqlServer", "src\ExtCore.Data.Dapper.SqlServer\ExtCore.Data.Dapper.SqlServer.csproj", "{E588921B-5E74-45E8-841B-2654D2416D9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.Data.Dapper.Sqlite", "src\ExtCore.Data.Dapper.Sqlite\ExtCore.Data.Dapper.Sqlite.csproj", "{693AB050-3305-471C-90C3-816BD694A930}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data.Dapper", "Data.Dapper", "{8C720480-750C-44C1-945B-961599088853}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileStorage", "FileStorage", "{2093A913-AB5F-4597-839E-A472E25D37D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.FileStorage", "src\ExtCore.FileStorage\ExtCore.FileStorage.csproj", "{AD4DB805-D932-4C5F-9559-B4704340F8CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.FileStorage.Abstractions", "src\ExtCore.FileStorage.Abstractions\ExtCore.FileStorage.Abstractions.csproj", "{EED241F9-4900-4FB0-91FD-432A5F40983D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.FileStorage.Dropbox", "src\ExtCore.FileStorage.Dropbox\ExtCore.FileStorage.Dropbox.csproj", "{D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.FileStorage.FileSystem", "src\ExtCore.FileStorage.FileSystem\ExtCore.FileStorage.FileSystem.csproj", "{2031A605-DB91-4998-94DB-8012C590152B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtCore.FileStorage.Azure", "src\ExtCore.FileStorage.Azure\ExtCore.FileStorage.Azure.csproj", "{6BDBDD17-3529-4A03-8DF1-25BEF8577E9B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,10 +87,6 @@ Global {BD3ADB01-9621-4A50-9D0E-8D4CC88A79D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD3ADB01-9621-4A50-9D0E-8D4CC88A79D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {BD3ADB01-9621-4A50-9D0E-8D4CC88A79D8}.Release|Any CPU.Build.0 = Release|Any CPU - {B713F760-F0A8-4A47-8FC0-2F08E85D5AF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B713F760-F0A8-4A47-8FC0-2F08E85D5AF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B713F760-F0A8-4A47-8FC0-2F08E85D5AF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B713F760-F0A8-4A47-8FC0-2F08E85D5AF9}.Release|Any CPU.Build.0 = Release|Any CPU {08204286-5843-46F5-B704-8F1183F7686E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08204286-5843-46F5-B704-8F1183F7686E}.Debug|Any CPU.Build.0 = Debug|Any CPU {08204286-5843-46F5-B704-8F1183F7686E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -83,24 +107,86 @@ Global {3E5C83A3-2C66-4503-A3F8-542854D3D2EF}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E5C83A3-2C66-4503-A3F8-542854D3D2EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E5C83A3-2C66-4503-A3F8-542854D3D2EF}.Release|Any CPU.Build.0 = Release|Any CPU + {77F8B77C-440C-48D9-80FA-192FF49283C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77F8B77C-440C-48D9-80FA-192FF49283C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77F8B77C-440C-48D9-80FA-192FF49283C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77F8B77C-440C-48D9-80FA-192FF49283C8}.Release|Any CPU.Build.0 = Release|Any CPU + {A97967FC-B333-4C27-BAFE-4369C7695688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A97967FC-B333-4C27-BAFE-4369C7695688}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A97967FC-B333-4C27-BAFE-4369C7695688}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A97967FC-B333-4C27-BAFE-4369C7695688}.Release|Any CPU.Build.0 = Release|Any CPU + {79A8C5FE-94FA-4D60-86C7-016EBF0386B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79A8C5FE-94FA-4D60-86C7-016EBF0386B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79A8C5FE-94FA-4D60-86C7-016EBF0386B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79A8C5FE-94FA-4D60-86C7-016EBF0386B4}.Release|Any CPU.Build.0 = Release|Any CPU + {976AD81D-4F7D-4A0C-9A2F-F665566EA5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {976AD81D-4F7D-4A0C-9A2F-F665566EA5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {976AD81D-4F7D-4A0C-9A2F-F665566EA5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {976AD81D-4F7D-4A0C-9A2F-F665566EA5A9}.Release|Any CPU.Build.0 = Release|Any CPU + {F27A3AE9-718C-4539-B541-4F37D16E4463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F27A3AE9-718C-4539-B541-4F37D16E4463}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F27A3AE9-718C-4539-B541-4F37D16E4463}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F27A3AE9-718C-4539-B541-4F37D16E4463}.Release|Any CPU.Build.0 = Release|Any CPU + {7B7A5A6C-DB45-4056-B5C7-4286D16220DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B7A5A6C-DB45-4056-B5C7-4286D16220DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B7A5A6C-DB45-4056-B5C7-4286D16220DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B7A5A6C-DB45-4056-B5C7-4286D16220DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E588921B-5E74-45E8-841B-2654D2416D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E588921B-5E74-45E8-841B-2654D2416D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E588921B-5E74-45E8-841B-2654D2416D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E588921B-5E74-45E8-841B-2654D2416D9F}.Release|Any CPU.Build.0 = Release|Any CPU + {693AB050-3305-471C-90C3-816BD694A930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {693AB050-3305-471C-90C3-816BD694A930}.Debug|Any CPU.Build.0 = Debug|Any CPU + {693AB050-3305-471C-90C3-816BD694A930}.Release|Any CPU.ActiveCfg = Release|Any CPU + {693AB050-3305-471C-90C3-816BD694A930}.Release|Any CPU.Build.0 = Release|Any CPU + {AD4DB805-D932-4C5F-9559-B4704340F8CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD4DB805-D932-4C5F-9559-B4704340F8CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD4DB805-D932-4C5F-9559-B4704340F8CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD4DB805-D932-4C5F-9559-B4704340F8CA}.Release|Any CPU.Build.0 = Release|Any CPU + {EED241F9-4900-4FB0-91FD-432A5F40983D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EED241F9-4900-4FB0-91FD-432A5F40983D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EED241F9-4900-4FB0-91FD-432A5F40983D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EED241F9-4900-4FB0-91FD-432A5F40983D}.Release|Any CPU.Build.0 = Release|Any CPU + {D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A}.Release|Any CPU.Build.0 = Release|Any CPU + {2031A605-DB91-4998-94DB-8012C590152B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2031A605-DB91-4998-94DB-8012C590152B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2031A605-DB91-4998-94DB-8012C590152B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2031A605-DB91-4998-94DB-8012C590152B}.Release|Any CPU.Build.0 = Release|Any CPU + {6BDBDD17-3529-4A03-8DF1-25BEF8577E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BDBDD17-3529-4A03-8DF1-25BEF8577E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BDBDD17-3529-4A03-8DF1-25BEF8577E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BDBDD17-3529-4A03-8DF1-25BEF8577E9B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {1A7B4E8E-9D6D-4DD2-862D-92A6BDDBC2C8} = {0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753} - {0F8F961C-2A13-47A2-9EFE-245FBA7A1C89} = {0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753} {E68DD4A8-1B91-4444-BD2F-4FDDBC69DEF1} = {7EB40884-B578-412E-951F-356BC69088C2} {64AAD007-11ED-4C0C-93D1-FBE2BEA81493} = {7EB40884-B578-412E-951F-356BC69088C2} {BD3ADB01-9621-4A50-9D0E-8D4CC88A79D8} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} - {B713F760-F0A8-4A47-8FC0-2F08E85D5AF9} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} {08204286-5843-46F5-B704-8F1183F7686E} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} - {A5B17E64-9C8B-40EB-A4FD-637BB1E1BD05} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} - {0C0A69C5-1036-4925-8063-C8E7F6999287} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} - {4B05A3AC-DCDF-454F-A105-C8DFDBFA5EE1} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} - {7EB40884-B578-412E-951F-356BC69088C2} = {0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753} - {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} = {0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753} - {B0915CA5-EB7A-428E-8A6E-344CB8CE5368} = {0D7AEFA3-ABB0-4AF1-A4C1-F8E05C241753} + {A5B17E64-9C8B-40EB-A4FD-637BB1E1BD05} = {A770A286-2B75-4BF7-8300-24CEC8D072AD} + {0C0A69C5-1036-4925-8063-C8E7F6999287} = {A770A286-2B75-4BF7-8300-24CEC8D072AD} + {4B05A3AC-DCDF-454F-A105-C8DFDBFA5EE1} = {A770A286-2B75-4BF7-8300-24CEC8D072AD} {3E5C83A3-2C66-4503-A3F8-542854D3D2EF} = {B0915CA5-EB7A-428E-8A6E-344CB8CE5368} + {77F8B77C-440C-48D9-80FA-192FF49283C8} = {A770A286-2B75-4BF7-8300-24CEC8D072AD} + {A97967FC-B333-4C27-BAFE-4369C7695688} = {DD95EC14-4783-4E08-A5EC-6C110DC7FB01} + {79A8C5FE-94FA-4D60-86C7-016EBF0386B4} = {A770A286-2B75-4BF7-8300-24CEC8D072AD} + {976AD81D-4F7D-4A0C-9A2F-F665566EA5A9} = {8C720480-750C-44C1-945B-961599088853} + {F27A3AE9-718C-4539-B541-4F37D16E4463} = {8C720480-750C-44C1-945B-961599088853} + {7B7A5A6C-DB45-4056-B5C7-4286D16220DE} = {8C720480-750C-44C1-945B-961599088853} + {E588921B-5E74-45E8-841B-2654D2416D9F} = {8C720480-750C-44C1-945B-961599088853} + {693AB050-3305-471C-90C3-816BD694A930} = {8C720480-750C-44C1-945B-961599088853} + {AD4DB805-D932-4C5F-9559-B4704340F8CA} = {2093A913-AB5F-4597-839E-A472E25D37D8} + {EED241F9-4900-4FB0-91FD-432A5F40983D} = {2093A913-AB5F-4597-839E-A472E25D37D8} + {D9FA3F92-D638-434D-9C0C-E74A6BCFDE7A} = {2093A913-AB5F-4597-839E-A472E25D37D8} + {2031A605-DB91-4998-94DB-8012C590152B} = {2093A913-AB5F-4597-839E-A472E25D37D8} + {6BDBDD17-3529-4A03-8DF1-25BEF8577E9B} = {2093A913-AB5F-4597-839E-A472E25D37D8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DF807CB9-3F57-4CF4-B501-26F9D4BB417C} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index a784d13..6588949 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ExtCore 1.2.0-beta1 +# ExtCore 9.0.0 [![Join the chat at https://gitter.im/ExtCore/ExtCore](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ExtCore/ExtCore?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -7,17 +7,16 @@ ## Introduction ExtCore is free, open source and cross-platform framework for creating modular and extendable web applications -based on ASP.NET Core. It is built using the best and the most modern tools and languages (Visual Studio 2015, C# +based on ASP.NET Core. It is built using the best and the most modern tools and languages (Visual Studio 2022, C# etc). Join our team! ExtCore allows you to build your web applications from the different independent reusable modules or extensions. Each of these modules or extensions may consist of one or more ASP.NET Core projects and each of these projects may include everything you want as any other ASP.NET Core project. You don’t need to perform any additional actions to make it all work: any ASP.NET Core project can be used as an ExtCore-based web application extension -by default. Controllers, view components, views (added as resources and/or precompiled), static content (added as -resources) are resolved automatically. These projects may be then added to the web application in two ways: as -direct dependencies (as source code or NuGet packages) or by copying compiled DLLs to the Extensions folder. -ExtCore supports both of these options out of the box and at the same time. +by default. Controllers, view components, precompiled views, static content (added as resources) are resolved automatically. +These projects may be then added to the web application in two ways: as direct dependencies (as source code or NuGet packages) +or by copying compiled DLLs to the Extensions folder. ExtCore supports both of these options out of the box and at the same time. Furthermore, any project of the ExtCore-based web application is able to discover the types that are defined inside all the projects (optionally using the predicates for assemblies filtering) and to get the implementations @@ -27,7 +26,7 @@ Any module or extension can execute its own code during the web application init use priorities to specify the correct order of the calls. This feature might be used for configuration, to register services etc. -ExtCore consists of two general packages and two optional basic extensions. +ExtCore consists of two general packages and four optional basic extensions. ### General Packages @@ -55,20 +54,24 @@ AssemblyProvider class which is used to discover assemblies and might be replace ExtCore basic extensions are: * ExtCore.Data; +* ExtCore.Data.EntityFramework; * ExtCore.Mvc; * ExtCore.Events. #### ExtCore.Data By default, ExtCore doesn’t know anything about data and storage, but you can use ExtCore.Data extension to have -unified approach to working with data and single storage context among all the extensions. Currently it supports -Microsoft SQL Server, PostgreSql and SQLite, but it is very easy to add another storage support. +unified approach to working with data and single storage context among all the extensions. Storage might be represented +by a database, a web API, a file structure or anything else. + +#### ExtCore.Data.EntityFramework + +Currently it supports MySQL, PostgreSql, SQLite, and SQL Server, but it is very easy to add another storage support. #### ExtCore.Mvc By default, ExtCore web applications are not MVC ones. MVC support is provided for them by ExtCore.Mvc extension. -This extension initializes MVC, makes it possible to use controllers, view components, views (added as resources -and/or precompiled), static content (added as resources) from other extensions etc. +This extension initializes MVC, makes it possible to use controllers, view components, precompiled views, static content (added as resources) from other extensions etc. #### ExtCore.Events @@ -81,23 +84,30 @@ You can find more information using the links at the bottom of this page. All you need to do to have modular and extendable web application is: * add ExtCore.WebApplication as dependency to your main web application project; -* inherit your main application’s Startup class from ExtCore.WebApplication.Startup; -* implement ExtCore.Infrastructure.IExtension interface in each of your extensions (optional); -* tell main web application about the extensions. +* call AddExtCore and UseExtCore inside your web application's Startup class; +* implement the ExtCore.Infrastructure.IConfigureServicesAction and IConfigureAction interfaces in your extensions +in order to execute some code inside the ConfigureServices and Configure methods of the web application's Startup class (optional); +* tell main web application about the extensions (with implicit dependencies or by copying them into the extensions folder). ### Samples Please take a look at our samples on GitHub: -* [Full-featured ExtCore 1.1.3 framework sample web application](https://github.com/ExtCore/ExtCore-Sample); -* [ExtCore Framework 1.1.3 Sample Simplest Web Application](https://github.com/ExtCore/ExtCore-Sample-Simplest); -* [ExtCore Framework 1.1.3 Sample MVC Web Application](https://github.com/ExtCore/ExtCore-Sample-Mvc); -* [ExtCore Framework 1.1.3 Sample Web Application That Uses a Database](https://github.com/ExtCore/ExtCore-Sample-Data); -* [ExtCore Framework 1.1.3 Sample Web Application with Modular UI](https://github.com/ExtCore/ExtCore-Sample-Modular-Ui); -* [ExtCore Framework 1.1.3 Sample Web Application That Registers a Service Inside the Extension](https://github.com/ExtCore/ExtCore-Sample-Service). - -You can also download our [ready to use full-featured sample](http://extcore.net/files/ExtCore-Sample-1.1.1.zip). -It contains everything you need to run ExtCore-based web application from Visual Studio 2015, including SQLite +* [Full-featured ExtCore 9.0.0 framework sample web application](https://github.com/ExtCore/ExtCore-Sample); +* [ExtCore framework 9.0.0 sample simplest web application](https://github.com/ExtCore/ExtCore-Sample-Simplest); +* [ExtCore framework 9.0.0 sample MVC web application](https://github.com/ExtCore/ExtCore-Sample-Mvc); +* [ExtCore framework 9.0.0 sample web application that uses file storage](https://github.com/ExtCore/ExtCore-Sample-FileStorage); +* [ExtCore framework 9.0.0 sample web application that uses a database](https://github.com/ExtCore/ExtCore-Sample-Data); +* [ExtCore framework 9.0.0 sample web application that uses Identity](https://github.com/ExtCore/ExtCore-Sample-Identity); +* [ExtCore framework 9.0.0 sample web application with modular UI](https://github.com/ExtCore/ExtCore-Sample-Modular-Ui); +* [ExtCore framework 9.0.0 advanced sample web application with modular UI](https://github.com/ExtCore/ExtCore-Sample-Modular-Ui-Adv); +* [ExtCore framework 9.0.0 advanced sample accounting web application](https://github.com/ExtCore/ExtCore-Sample-Accounting); +* [ExtCore framework 9.0.0 sample web application that registers a service inside the extension](https://github.com/ExtCore/ExtCore-Sample-Service); +* [ExtCore framework 9.0.0 sample web application that uses the events](https://github.com/ExtCore/ExtCore-Sample-Events); +* [ExtCore framework 9.0.0 sample API web application](https://github.com/ExtCore/ExtCore-Sample-Api). + +You can also download our [ready to use full-featured sample](http://extcore.net/files/ExtCore-Sample-9.0.0.zip). +It contains everything you need to run ExtCore-based web application from Visual Studio 2022, including SQLite database with the test data. ### Tutorials @@ -108,7 +118,7 @@ to help you start developing your ExtCore-based web applications. ### Real Projects Please take a look at [Platformus](https://github.com/Platformus/Platformus) on GitHub. It is CMS -built on ExtCore framework with 10 extensions and 70 projects. +built on ExtCore framework with 3 extensions and 25 projects. ## Development and Debug diff --git a/extcore.png b/extcore.png new file mode 100644 index 0000000..acb7db2 Binary files /dev/null and b/extcore.png differ diff --git a/global.json b/global.json deleted file mode 100644 index bd52dc5..0000000 --- a/global.json +++ /dev/null @@ -1 +0,0 @@ -{"projects":["src"]} \ No newline at end of file diff --git a/src/ExtCore.Data.Abstractions/ExtCore.Data.Abstractions.csproj b/src/ExtCore.Data.Abstractions/ExtCore.Data.Abstractions.csproj index 5c0e304..186297d 100644 --- a/src/ExtCore.Data.Abstractions/ExtCore.Data.Abstractions.csproj +++ b/src/ExtCore.Data.Abstractions/ExtCore.Data.Abstractions.csproj @@ -1,16 +1,11 @@  + - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Data extension component. Based on the ExtCore framework. + netstandard2.0 ExtCore.Data.Abstractions ExtCore.Data.Abstractions - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - 1.6.1 diff --git a/src/ExtCore.Data.Abstractions/IRepository.cs b/src/ExtCore.Data.Abstractions/IRepository.cs index aa65ad8..afee570 100644 --- a/src/ExtCore.Data.Abstractions/IRepository.cs +++ b/src/ExtCore.Data.Abstractions/IRepository.cs @@ -1,17 +1,16 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ExtCore.Data.Abstractions +namespace ExtCore.Data.Abstractions; + +/// +/// Describes a repository for working with the underlying storage context. +/// +public interface IRepository { /// - /// Describes a repository for working with the underlying storage context. + /// Sets the storage context to work with. /// - public interface IRepository - { - /// - /// Sets the storage context to work with. - /// - /// The storage context to set. - void SetStorageContext(IStorageContext storageContext); - } + /// The storage context to set. + void SetStorageContext(IStorageContext storageContext); } \ No newline at end of file diff --git a/src/ExtCore.Data.Abstractions/IStorage.cs b/src/ExtCore.Data.Abstractions/IStorage.cs index ff145b5..778382e 100644 --- a/src/ExtCore.Data.Abstractions/IStorage.cs +++ b/src/ExtCore.Data.Abstractions/IStorage.cs @@ -1,25 +1,36 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ExtCore.Data.Abstractions +using System.Threading.Tasks; + +namespace ExtCore.Data.Abstractions; + +/// +/// Describes a storage that is implementation of the Unit of Work design pattern with the mechanism +/// of getting the repositories to work with the underlying storage context and committing the changes +/// made by all the repositories. +/// +public interface IStorage { /// - /// Describes a storage that is implementation of the Unit of Work design pattern with the mechanism - /// of getting the repositories to work with the underlying storage context and committing the changes - /// made by all the repositories. + /// Gets the underlying storage context used by this storage. + /// + IStorageContext StorageContext { get; } + + /// + /// Gets a repository of the given type. /// - public interface IStorage - { - /// - /// Gets a repository of the given type. - /// - /// The type parameter to find implementation of. - /// - T GetRepository() where T: IRepository; + /// The type parameter to find implementation of. + /// + T GetRepository() where T: IRepository; - /// - /// Commits the changes made by all the repositories. - /// - void Save(); - } + /// + /// Commits the changes made by all the repositories. + /// + int Save(); + + /// + /// Asynchronously commits the changes made by all the repositories. + /// + Task SaveAsync(); } \ No newline at end of file diff --git a/src/ExtCore.Data.Abstractions/IStorageContext.cs b/src/ExtCore.Data.Abstractions/IStorageContext.cs index b9aa6f2..89055ad 100644 --- a/src/ExtCore.Data.Abstractions/IStorageContext.cs +++ b/src/ExtCore.Data.Abstractions/IStorageContext.cs @@ -1,13 +1,12 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ExtCore.Data.Abstractions +namespace ExtCore.Data.Abstractions; + +/// +/// Describes a storage context that must be shared among all the repositories. Storage context +/// represents concrete storage (for example, SQLite database). +/// +public interface IStorageContext { - /// - /// Describes a storage context that must be shared among all the repositories. Storage context - /// represents concrete storage (for example, SQLite database). - /// - public interface IStorageContext - { - } } \ No newline at end of file diff --git a/src/ExtCore.Data.Abstractions/icon.png b/src/ExtCore.Data.Abstractions/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Abstractions/icon.png differ diff --git a/src/ExtCore.Data.Dapper.MySql/ExtCore.Data.Dapper.MySql.csproj b/src/ExtCore.Data.Dapper.MySql/ExtCore.Data.Dapper.MySql.csproj new file mode 100644 index 0000000..874a62b --- /dev/null +++ b/src/ExtCore.Data.Dapper.MySql/ExtCore.Data.Dapper.MySql.csproj @@ -0,0 +1,19 @@ + + + + + The ExtCore.Data.Dapper extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.Dapper.MySql + ExtCore.Data.Dapper.MySql + + + + + + + + + + + diff --git a/src/ExtCore.Data.Dapper.MySql/StorageContext.cs b/src/ExtCore.Data.Dapper.MySql/StorageContext.cs new file mode 100644 index 0000000..9ede0bb --- /dev/null +++ b/src/ExtCore.Data.Dapper.MySql/StorageContext.cs @@ -0,0 +1,22 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.Dapper.MySql; + +/// +/// Implements the IStorageContext interface and represents MySQL database +/// with the Dapper as the ORM. +/// +public class StorageContext : StorageContextBase +{ + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The options that are used to connect to the MySQL database. + public StorageContext(IOptions options) + : base(options) + { + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper.MySql/icon.png b/src/ExtCore.Data.Dapper.MySql/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Dapper.MySql/icon.png differ diff --git a/src/ExtCore.Data.Dapper.PostgreSql/ExtCore.Data.Dapper.PostgreSql.csproj b/src/ExtCore.Data.Dapper.PostgreSql/ExtCore.Data.Dapper.PostgreSql.csproj new file mode 100644 index 0000000..1796e32 --- /dev/null +++ b/src/ExtCore.Data.Dapper.PostgreSql/ExtCore.Data.Dapper.PostgreSql.csproj @@ -0,0 +1,19 @@ + + + + + The ExtCore.Data.Dapper extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.Dapper.PostgreSql + ExtCore.Data.Dapper.PostgreSql + + + + + + + + + + + diff --git a/src/ExtCore.Data.Dapper.PostgreSql/StorageContext.cs b/src/ExtCore.Data.Dapper.PostgreSql/StorageContext.cs new file mode 100644 index 0000000..0764bca --- /dev/null +++ b/src/ExtCore.Data.Dapper.PostgreSql/StorageContext.cs @@ -0,0 +1,22 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.Dapper.PostgreSql; + +/// +/// Implements the IStorageContext interface and represents PostgreSQL database +/// with the Dapper as the ORM. +/// +public class StorageContext : StorageContextBase +{ + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The options that are used to connect to the PostgreSQL database. + public StorageContext(IOptions options) + : base(options) + { + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper.PostgreSql/icon.png b/src/ExtCore.Data.Dapper.PostgreSql/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Dapper.PostgreSql/icon.png differ diff --git a/src/ExtCore.Data.Dapper.SqlServer/ExtCore.Data.Dapper.SqlServer.csproj b/src/ExtCore.Data.Dapper.SqlServer/ExtCore.Data.Dapper.SqlServer.csproj new file mode 100644 index 0000000..87694c6 --- /dev/null +++ b/src/ExtCore.Data.Dapper.SqlServer/ExtCore.Data.Dapper.SqlServer.csproj @@ -0,0 +1,15 @@ + + + + + The ExtCore.Data.Dapper extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.Dapper.SqlServer + ExtCore.Data.Dapper.SqlServer + + + + + + + diff --git a/src/ExtCore.Data.Dapper.SqlServer/StorageContext.cs b/src/ExtCore.Data.Dapper.SqlServer/StorageContext.cs new file mode 100644 index 0000000..afcafc3 --- /dev/null +++ b/src/ExtCore.Data.Dapper.SqlServer/StorageContext.cs @@ -0,0 +1,22 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.Dapper.SqlServer; + +/// +/// Implements the IStorageContext interface and represents SQL Server database +/// with the Dapper as the ORM. +/// +public class StorageContext : StorageContextBase +{ + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The options that are used to connect to the SQL Server database. + public StorageContext(IOptions options) + : base(options) + { + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper.SqlServer/icon.png b/src/ExtCore.Data.Dapper.SqlServer/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Dapper.SqlServer/icon.png differ diff --git a/src/ExtCore.Data.Dapper.Sqlite/ExtCore.Data.Dapper.Sqlite.csproj b/src/ExtCore.Data.Dapper.Sqlite/ExtCore.Data.Dapper.Sqlite.csproj new file mode 100644 index 0000000..bb9ba4b --- /dev/null +++ b/src/ExtCore.Data.Dapper.Sqlite/ExtCore.Data.Dapper.Sqlite.csproj @@ -0,0 +1,19 @@ + + + + + The ExtCore.Data.Dapper extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.Dapper.Sqlite + ExtCore.Data.Dapper.Sqlite + + + + + + + + + + + diff --git a/src/ExtCore.Data.Dapper.Sqlite/StorageContext.cs b/src/ExtCore.Data.Dapper.Sqlite/StorageContext.cs new file mode 100644 index 0000000..e981d95 --- /dev/null +++ b/src/ExtCore.Data.Dapper.Sqlite/StorageContext.cs @@ -0,0 +1,22 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.Dapper.Sqlite; + +/// +/// Implements the IStorageContext interface and represents SQLite database +/// with the Dapper as the ORM. +/// +public class StorageContext : StorageContextBase +{ + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The options that are used to connect to the SQLite database. + public StorageContext(IOptions options) + : base(options) + { + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper.Sqlite/icon.png b/src/ExtCore.Data.Dapper.Sqlite/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Dapper.Sqlite/icon.png differ diff --git a/src/ExtCore.Data.Dapper/Actions/AddStorageContextAction.cs b/src/ExtCore.Data.Dapper/Actions/AddStorageContextAction.cs new file mode 100644 index 0000000..827df3f --- /dev/null +++ b/src/ExtCore.Data.Dapper/Actions/AddStorageContextAction.cs @@ -0,0 +1,49 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.Data.Dapper.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// registers found implementation of the IStorageContext interface inside the DI. +/// +public class AddStorageContextAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Registers found implementation of the IStorageContext interface inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + Type type = ExtensionManager.GetImplementations()?.FirstOrDefault(t => !t.GetTypeInfo().IsAbstract); + + if (type == null) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.Data.Dapper"); + + logger.LogError("Implementation of ExtCore.Data.Abstractions.IStorageContext not found"); + return; + } + + services.AddScoped(typeof(IStorageContext), type); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/ExtCore.Data.Dapper.csproj b/src/ExtCore.Data.Dapper/ExtCore.Data.Dapper.csproj new file mode 100644 index 0000000..367721d --- /dev/null +++ b/src/ExtCore.Data.Dapper/ExtCore.Data.Dapper.csproj @@ -0,0 +1,21 @@ + + + + + The ExtCore.Data.Dapper extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.Dapper + ExtCore.Data.Dapper + + + + + + + + + + + + + diff --git a/src/ExtCore.Data.Dapper/Extension.cs b/src/ExtCore.Data.Dapper/Extension.cs new file mode 100644 index 0000000..9de1801 --- /dev/null +++ b/src/ExtCore.Data.Dapper/Extension.cs @@ -0,0 +1,33 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.Data.Dapper; + +/// +/// Overrides the ExtensionBase class and provides the +/// ExtCore.Data.Dapper extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.Data.Dapper"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/RepositoryBase.cs b/src/ExtCore.Data.Dapper/RepositoryBase.cs new file mode 100644 index 0000000..f94302b --- /dev/null +++ b/src/ExtCore.Data.Dapper/RepositoryBase.cs @@ -0,0 +1,27 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Data.Abstractions; +using ExtCore.Data.Entities.Abstractions; + +namespace ExtCore.Data.Dapper; + +/// +/// Implements the IRepository interface and represents default repository behavior. +/// +/// The entity type this repository operates. +public abstract class RepositoryBase : IRepository where TEntity : class, IEntity +{ + protected IStorageContext storageContext; + protected string connectionString; + + /// + /// Sets the Dapper storage context that represents the physical storage to work with. + /// + /// The Dapper storage context to set. + public void SetStorageContext(IStorageContext storageContext) + { + this.storageContext = storageContext; + this.connectionString = (storageContext as StorageContextBase).ConnectionString; + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/Storage.cs b/src/ExtCore.Data.Dapper/Storage.cs new file mode 100644 index 0000000..2d5a3a2 --- /dev/null +++ b/src/ExtCore.Data.Dapper/Storage.cs @@ -0,0 +1,57 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; + +namespace ExtCore.Data.Dapper; + +/// +/// Implements the IStorage interface and represents implementation of the +/// Unit of Work design pattern with the mechanism of getting the repositories to work with the underlying +/// Dapper storage context and committing the changes made by all the repositories. +/// +public class Storage : IStorage +{ + /// + /// Gets the Dapper storage context. + /// + public IStorageContext StorageContext { get; private set; } + + public Storage(IStorageContext storageContext) + { + this.StorageContext = storageContext; + } + + /// + /// Gets a repository of the given type. + /// + /// The type parameter to find implementation of. + /// + public TRepository GetRepository() where TRepository : IRepository + { + TRepository repository = ExtensionManager.GetInstance(); + + if (repository != null) + repository.SetStorageContext(this.StorageContext); + + return repository; + } + + /// + /// Commits the changes made by all the repositories. + /// + public int Save() + { + return -1; + } + + /// + /// Asynchronously commits the changes made by all the repositories. + /// + public async Task SaveAsync() + { + return -1; + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/StorageContextBase.cs b/src/ExtCore.Data.Dapper/StorageContextBase.cs new file mode 100644 index 0000000..e8d7872 --- /dev/null +++ b/src/ExtCore.Data.Dapper/StorageContextBase.cs @@ -0,0 +1,28 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Data.Abstractions; +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.Dapper; + +/// +/// Implements the IStorageContext interface and represents the physical storage +/// with the Dapper Core as the ORM. +/// +public abstract class StorageContextBase : IStorageContext +{ + /// + /// The connection string that is used to connect to the physical storage. + /// + public string ConnectionString { get; private set; } + + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The connection string that is used to connect to the physical storage. + public StorageContextBase(IOptions options) + { + this.ConnectionString = options.Value.ConnectionString; + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/StorageContextOptions.cs b/src/ExtCore.Data.Dapper/StorageContextOptions.cs new file mode 100644 index 0000000..5691a6b --- /dev/null +++ b/src/ExtCore.Data.Dapper/StorageContextOptions.cs @@ -0,0 +1,15 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace ExtCore.Data.Dapper; + +/// +/// Represents Dapper storage context options. +/// +public class StorageContextOptions +{ + /// + /// The connection string that is used to connect to the physical storage. + /// + public string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/src/ExtCore.Data.Dapper/icon.png b/src/ExtCore.Data.Dapper/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Dapper/icon.png differ diff --git a/src/ExtCore.Data.Entities.Abstractions/ExtCore.Data.Entities.Abstractions.csproj b/src/ExtCore.Data.Entities.Abstractions/ExtCore.Data.Entities.Abstractions.csproj new file mode 100644 index 0000000..023a3dd --- /dev/null +++ b/src/ExtCore.Data.Entities.Abstractions/ExtCore.Data.Entities.Abstractions.csproj @@ -0,0 +1,11 @@ + + + + + The ExtCore.Data extension component. Based on the ExtCore framework. + netstandard2.0 + ExtCore.Data.Entities.Abstractions + ExtCore.Data.Entities.Abstractions + + + diff --git a/src/ExtCore.Data.Models.Abstractions/IEntity.cs b/src/ExtCore.Data.Entities.Abstractions/IEntity.cs similarity index 56% rename from src/ExtCore.Data.Models.Abstractions/IEntity.cs rename to src/ExtCore.Data.Entities.Abstractions/IEntity.cs index f306cf8..2208af2 100644 --- a/src/ExtCore.Data.Models.Abstractions/IEntity.cs +++ b/src/ExtCore.Data.Entities.Abstractions/IEntity.cs @@ -1,12 +1,11 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ExtCore.Data.Models.Abstractions +namespace ExtCore.Data.Entities.Abstractions; + +/// +/// Describes an entity. +/// +public interface IEntity { - /// - /// Describes an entity. - /// - public interface IEntity - { - } } \ No newline at end of file diff --git a/src/ExtCore.Data.Entities.Abstractions/icon.png b/src/ExtCore.Data.Entities.Abstractions/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.Entities.Abstractions/icon.png differ diff --git a/src/ExtCore.Data.EntityFramework.MySql/ExtCore.Data.EntityFramework.MySql.csproj b/src/ExtCore.Data.EntityFramework.MySql/ExtCore.Data.EntityFramework.MySql.csproj new file mode 100644 index 0000000..41554ce --- /dev/null +++ b/src/ExtCore.Data.EntityFramework.MySql/ExtCore.Data.EntityFramework.MySql.csproj @@ -0,0 +1,19 @@ + + + + + The ExtCore.Data.EntityFramework extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.EntityFramework.MySql + ExtCore.Data.EntityFramework.MySql + + + + + + + + + + + diff --git a/src/ExtCore.Data.EntityFramework.MySql/StorageContext.cs b/src/ExtCore.Data.EntityFramework.MySql/StorageContext.cs new file mode 100644 index 0000000..3daec68 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework.MySql/StorageContext.cs @@ -0,0 +1,36 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.EntityFramework.MySql; + +/// +/// Implements the IStorageContext interface and represents MySQL database +/// with the Entity Framework Core as the ORM. +/// +public class StorageContext : StorageContextBase +{ + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The options that are used to connect to the MySQL database. + public StorageContext(IOptions options) : base(options) { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + + if (string.IsNullOrEmpty(this.MigrationsAssembly)) + optionsBuilder.UseMySQL(this.ConnectionString); + + else optionsBuilder.UseMySQL( + this.ConnectionString, + options => + { + options.MigrationsAssembly(this.MigrationsAssembly); + } + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.MySql/icon.png b/src/ExtCore.Data.EntityFramework.MySql/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.EntityFramework.MySql/icon.png differ diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/ExtCore.Data.EntityFramework.PostgreSql.csproj b/src/ExtCore.Data.EntityFramework.PostgreSql/ExtCore.Data.EntityFramework.PostgreSql.csproj index d52b998..a0f4bbf 100644 --- a/src/ExtCore.Data.EntityFramework.PostgreSql/ExtCore.Data.EntityFramework.PostgreSql.csproj +++ b/src/ExtCore.Data.EntityFramework.PostgreSql/ExtCore.Data.EntityFramework.PostgreSql.csproj @@ -1,25 +1,19 @@  + - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Data.EntityFramework extension component. Based on the ExtCore framework. + net8.0 ExtCore.Data.EntityFramework.PostgreSql ExtCore.Data.EntityFramework.PostgreSql - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - - - + - + diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/IModelRegistrar.cs b/src/ExtCore.Data.EntityFramework.PostgreSql/IModelRegistrar.cs deleted file mode 100644 index 18f56f9..0000000 --- a/src/ExtCore.Data.EntityFramework.PostgreSql/IModelRegistrar.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.PostgreSql -{ - /// - /// Describes a mechanism of registering entities inside the PostgreSQL storage context. - /// - public interface IModelRegistrar - { - /// - /// Registers entities inside the PostgreSQL storage context. - /// - /// The Entity Framework model builder. - void RegisterModels(ModelBuilder modelbuilder); - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/RepositoryBase.cs b/src/ExtCore.Data.EntityFramework.PostgreSql/RepositoryBase.cs deleted file mode 100644 index 341236a..0000000 --- a/src/ExtCore.Data.EntityFramework.PostgreSql/RepositoryBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Data.Models.Abstractions; -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.PostgreSql -{ - /// - /// Implements the IRepository interface and represents default repository behavior. - /// - public abstract class RepositoryBase : IRepository where TEntity : class, IEntity - { - protected StorageContext storageContext; - protected DbSet dbSet; - - /// - /// Sets the storage context that represents the PostgreSQL database to work with. - /// - /// The storage context to set. - public void SetStorageContext(IStorageContext storageContext) - { - this.storageContext = storageContext as StorageContext; - this.dbSet = this.storageContext.Set(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/Storage.cs b/src/ExtCore.Data.EntityFramework.PostgreSql/Storage.cs deleted file mode 100644 index 96aef79..0000000 --- a/src/ExtCore.Data.EntityFramework.PostgreSql/Storage.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; - -namespace ExtCore.Data.EntityFramework.PostgreSql -{ - /// - /// Implements the IStorage interface and represents implementation of the - /// Unit of Work design pattern with the mechanism of getting the repositories to work with the underlying - /// PostgreSQL database storage context and committing the changes made by all the repositories. - /// - public class Storage : IStorage - { - /// - /// Gets or sets the connection string that is used to connect to the PostgreSQL database. - /// - public static string ConnectionString { get; set; } - - /// - /// Gets or sets the storage context that represents the PostgreSQL database. - /// - public StorageContext StorageContext { get; private set; } - - /// - /// Initializes a new instance of the Storage class. - /// - public Storage() - { - this.StorageContext = new StorageContext(Storage.ConnectionString); - } - - /// - /// Gets a repository of the given type. - /// - /// The type parameter to find implementation of. - /// - public TRepository GetRepository() where TRepository : IRepository - { - TRepository repository = ExtensionManager.GetInstance(a => a.FullName.ToLower().Contains("entityframework.postgresql")); - - repository.SetStorageContext(this.StorageContext); - return repository; - } - - /// - /// Commits the changes made by all the repositories. - /// - public void Save() - { - this.StorageContext.SaveChanges(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/StorageContext.cs b/src/ExtCore.Data.EntityFramework.PostgreSql/StorageContext.cs index 883700a..099d407 100644 --- a/src/ExtCore.Data.EntityFramework.PostgreSql/StorageContext.cs +++ b/src/ExtCore.Data.EntityFramework.PostgreSql/StorageContext.cs @@ -1,41 +1,36 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; -namespace ExtCore.Data.EntityFramework.PostgreSql +namespace ExtCore.Data.EntityFramework.PostgreSql; + +/// +/// Implements the IStorageContext interface and represents PostgreSQL database +/// with the Entity Framework Core as the ORM. +/// +public class StorageContext : StorageContextBase { /// - /// Implements the IStorageContext interface and represents PostgreSQL database - /// with the Entity Framework Core as the ORM. + /// Initializes a new instance of the StorageContext class. /// - public class StorageContext : DbContext, IStorageContext - { - private string connectionString { get; set; } + /// The options that are used to connect to the PostgreSQL database. + public StorageContext(IOptions options) : base(options) { } - /// - /// Initializes a new instance of the StorageContext class. - /// - /// The connection string that is used to connect to the PostgreSQL database. - public StorageContext(string connectionString) - { - this.connectionString = connectionString; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder.UseNpgsql(this.connectionString); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + if (string.IsNullOrEmpty(this.MigrationsAssembly)) + optionsBuilder.UseNpgsql(this.ConnectionString); - foreach (IModelRegistrar modelRegistrar in ExtensionManager.GetInstances(a => a.FullName.ToLower().Contains("entityframework.postgresql"))) - modelRegistrar.RegisterModels(modelBuilder); - } + else optionsBuilder.UseNpgsql( + this.ConnectionString, + options => + { + options.MigrationsAssembly(this.MigrationsAssembly); + } + ); } } \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.PostgreSql/icon.png b/src/ExtCore.Data.EntityFramework.PostgreSql/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.EntityFramework.PostgreSql/icon.png differ diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/ExtCore.Data.EntityFramework.SqlServer.csproj b/src/ExtCore.Data.EntityFramework.SqlServer/ExtCore.Data.EntityFramework.SqlServer.csproj index 05ac72b..b9a464a 100644 --- a/src/ExtCore.Data.EntityFramework.SqlServer/ExtCore.Data.EntityFramework.SqlServer.csproj +++ b/src/ExtCore.Data.EntityFramework.SqlServer/ExtCore.Data.EntityFramework.SqlServer.csproj @@ -1,25 +1,19 @@  + - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Data.EntityFramework extension component. Based on the ExtCore framework. + net8.0 ExtCore.Data.EntityFramework.SqlServer ExtCore.Data.EntityFramework.SqlServer - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - - - + - + diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/IModelRegistrar.cs b/src/ExtCore.Data.EntityFramework.SqlServer/IModelRegistrar.cs deleted file mode 100644 index 92f0f25..0000000 --- a/src/ExtCore.Data.EntityFramework.SqlServer/IModelRegistrar.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.SqlServer -{ - /// - /// Describes a mechanism of registering entities inside the SQL Server storage context. - /// - public interface IModelRegistrar - { - /// - /// Registers entities inside the SQL Server storage context. - /// - /// The Entity Framework model builder. - void RegisterModels(ModelBuilder modelbuilder); - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/RepositoryBase.cs b/src/ExtCore.Data.EntityFramework.SqlServer/RepositoryBase.cs deleted file mode 100644 index 391dba3..0000000 --- a/src/ExtCore.Data.EntityFramework.SqlServer/RepositoryBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Data.Models.Abstractions; -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.SqlServer -{ - /// - /// Implements the IRepository interface and represents default repository behavior. - /// - public abstract class RepositoryBase : IRepository where TEntity : class, IEntity - { - protected StorageContext storageContext; - protected DbSet dbSet; - - /// - /// Sets the storage context that represents the SQL Server database to work with. - /// - /// The storage context to set. - public void SetStorageContext(IStorageContext storageContext) - { - this.storageContext = storageContext as StorageContext; - this.dbSet = this.storageContext.Set(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/Storage.cs b/src/ExtCore.Data.EntityFramework.SqlServer/Storage.cs deleted file mode 100644 index 316e21c..0000000 --- a/src/ExtCore.Data.EntityFramework.SqlServer/Storage.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; - -namespace ExtCore.Data.EntityFramework.SqlServer -{ - /// - /// Implements the IStorage interface and represents implementation of the - /// Unit of Work design pattern with the mechanism of getting the repositories to work with the underlying - /// SQL Server database storage context and committing the changes made by all the repositories. - /// - public class Storage : IStorage - { - /// - /// Gets or sets the connection string that is used to connect to the SQL Server database. - /// - public static string ConnectionString { get; set; } - - /// - /// Gets or sets the storage context that represents the SQL Server database. - /// - public StorageContext StorageContext { get; private set; } - - /// - /// Initializes a new instance of the Storage class. - /// - public Storage() - { - this.StorageContext = new StorageContext(Storage.ConnectionString); - } - - /// - /// Gets a repository of the given type. - /// - /// The type parameter to find implementation of. - /// - public TRepository GetRepository() where TRepository : IRepository - { - TRepository repository = ExtensionManager.GetInstance(a => a.FullName.ToLower().Contains("entityframework.sqlserver")); - - repository.SetStorageContext(this.StorageContext); - return repository; - } - - /// - /// Commits the changes made by all the repositories. - /// - public void Save() - { - this.StorageContext.SaveChanges(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/StorageContext.cs b/src/ExtCore.Data.EntityFramework.SqlServer/StorageContext.cs index 7aa30b1..1665173 100644 --- a/src/ExtCore.Data.EntityFramework.SqlServer/StorageContext.cs +++ b/src/ExtCore.Data.EntityFramework.SqlServer/StorageContext.cs @@ -1,41 +1,36 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; -namespace ExtCore.Data.EntityFramework.SqlServer +namespace ExtCore.Data.EntityFramework.SqlServer; + +/// +/// Implements the IStorageContext interface and represents SQL Server database +/// with the Entity Framework Core as the ORM. +/// +public class StorageContext : StorageContextBase { /// - /// Implements the IStorageContext interface and represents SQL Server database - /// with the Entity Framework Core as the ORM. + /// Initializes a new instance of the StorageContext class. /// - public class StorageContext : DbContext, IStorageContext - { - private string connectionString { get; set; } + /// The options that are used to connect to the SQL Server database. + public StorageContext(IOptions options) : base(options) { } - /// - /// Initializes a new instance of the StorageContext class. - /// - /// The connection string that is used to connect to the SQL Server database. - public StorageContext(string connectionString) - { - this.connectionString = connectionString; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlServer(this.connectionString); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + if (string.IsNullOrEmpty(this.MigrationsAssembly)) + optionsBuilder.UseSqlServer(this.ConnectionString); - foreach (IModelRegistrar modelRegistrar in ExtensionManager.GetInstances(a => a.FullName.ToLower().Contains("entityframework.sqlserver"))) - modelRegistrar.RegisterModels(modelBuilder); - } + else optionsBuilder.UseSqlServer( + this.ConnectionString, + options => + { + options.MigrationsAssembly(this.MigrationsAssembly); + } + ); } } \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.SqlServer/icon.png b/src/ExtCore.Data.EntityFramework.SqlServer/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.EntityFramework.SqlServer/icon.png differ diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/ExtCore.Data.EntityFramework.Sqlite.csproj b/src/ExtCore.Data.EntityFramework.Sqlite/ExtCore.Data.EntityFramework.Sqlite.csproj index f199494..ec6bad0 100644 --- a/src/ExtCore.Data.EntityFramework.Sqlite/ExtCore.Data.EntityFramework.Sqlite.csproj +++ b/src/ExtCore.Data.EntityFramework.Sqlite/ExtCore.Data.EntityFramework.Sqlite.csproj @@ -1,25 +1,19 @@  + - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Data.EntityFramework extension component. Based on the ExtCore framework. + net8.0 ExtCore.Data.EntityFramework.Sqlite ExtCore.Data.EntityFramework.Sqlite - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - - - + - + diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/IModelRegistrar.cs b/src/ExtCore.Data.EntityFramework.Sqlite/IModelRegistrar.cs deleted file mode 100644 index fe9f3db..0000000 --- a/src/ExtCore.Data.EntityFramework.Sqlite/IModelRegistrar.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.Sqlite -{ - /// - /// Describes a mechanism of registering entities inside the SQLite storage context. - /// - public interface IModelRegistrar - { - /// - /// Registers entities inside the SQLite storage context. - /// - /// The Entity Framework model builder. - void RegisterModels(ModelBuilder modelbuilder); - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/RepositoryBase.cs b/src/ExtCore.Data.EntityFramework.Sqlite/RepositoryBase.cs deleted file mode 100644 index a1a7620..0000000 --- a/src/ExtCore.Data.EntityFramework.Sqlite/RepositoryBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Data.Models.Abstractions; -using Microsoft.EntityFrameworkCore; - -namespace ExtCore.Data.EntityFramework.Sqlite -{ - /// - /// Implements the IRepository interface and represents default repository behavior. - /// - public abstract class RepositoryBase : IRepository where TEntity : class, IEntity - { - protected StorageContext storageContext; - protected DbSet dbSet; - - /// - /// Sets the storage context that represents the SQLite database to work with. - /// - /// The storage context to set. - public void SetStorageContext(IStorageContext storageContext) - { - this.storageContext = storageContext as StorageContext; - this.dbSet = this.storageContext.Set(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/Storage.cs b/src/ExtCore.Data.EntityFramework.Sqlite/Storage.cs deleted file mode 100644 index e6610cb..0000000 --- a/src/ExtCore.Data.EntityFramework.Sqlite/Storage.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; - -namespace ExtCore.Data.EntityFramework.Sqlite -{ - /// - /// Implements the IStorage interface and represents implementation of the - /// Unit of Work design pattern with the mechanism of getting the repositories to work with the underlying - /// SQLite database storage context and committing the changes made by all the repositories. - /// - public class Storage : IStorage - { - /// - /// Gets or sets the connection string that is used to connect to the SQLite database. - /// - public static string ConnectionString { get; set; } - - /// - /// Gets or sets the storage context that represents the SQLite database. - /// - public StorageContext StorageContext { get; private set; } - - /// - /// Initializes a new instance of the Storage class. - /// - public Storage() - { - this.StorageContext = new StorageContext(Storage.ConnectionString); - } - - /// - /// Gets a repository of the given type. - /// - /// The type parameter to find implementation of. - /// - public TRepository GetRepository() where TRepository : IRepository - { - TRepository repository = ExtensionManager.GetInstance(a => a.FullName.ToLower().Contains("entityframework.sqlite")); - - repository.SetStorageContext(this.StorageContext); - return repository; - } - - /// - /// Commits the changes made by all the repositories. - /// - public void Save() - { - this.StorageContext.SaveChanges(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/StorageContext.cs b/src/ExtCore.Data.EntityFramework.Sqlite/StorageContext.cs index 940a33a..90ee51a 100644 --- a/src/ExtCore.Data.EntityFramework.Sqlite/StorageContext.cs +++ b/src/ExtCore.Data.EntityFramework.Sqlite/StorageContext.cs @@ -1,41 +1,39 @@ // Copyright © 2015 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; -namespace ExtCore.Data.EntityFramework.Sqlite +namespace ExtCore.Data.EntityFramework.Sqlite; + +/// +/// Implements the IStorageContext interface and represents SQLite database +/// with the Entity Framework Core as the ORM. +/// +public class StorageContext : StorageContextBase { /// - /// Implements the IStorageContext interface and represents SQLite database - /// with the Entity Framework Core as the ORM. + /// Initializes a new instance of the StorageContext class. /// - public class StorageContext : DbContext, IStorageContext + /// The options that are used to connect to the SQLite database. + public StorageContext(IOptions options) + : base(options) { - private string connectionString { get; set; } - - /// - /// Initializes a new instance of the StorageContext class. - /// - /// The connection string that is used to connect to the SQLite database. - public StorageContext(string connectionString) - { - this.connectionString = connectionString; - } + } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(this.connectionString); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + if (string.IsNullOrEmpty(this.MigrationsAssembly)) + optionsBuilder.UseSqlite(this.ConnectionString); - foreach (IModelRegistrar modelRegistrar in ExtensionManager.GetInstances(a => a.FullName.ToLower().Contains("entityframework.sqlite"))) - modelRegistrar.RegisterModels(modelBuilder); - } + else optionsBuilder.UseSqlite( + this.ConnectionString, + options => + { + options.MigrationsAssembly(this.MigrationsAssembly); + } + ); } } \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework.Sqlite/icon.png b/src/ExtCore.Data.EntityFramework.Sqlite/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.EntityFramework.Sqlite/icon.png differ diff --git a/src/ExtCore.Data.EntityFramework/Actions/AddStorageContextAction.cs b/src/ExtCore.Data.EntityFramework/Actions/AddStorageContextAction.cs new file mode 100644 index 0000000..045e870 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/Actions/AddStorageContextAction.cs @@ -0,0 +1,49 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.Data.EntityFramework.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// registers found implementation of the IStorageContext interface inside the DI. +/// +public class AddStorageContextAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Registers found implementation of the IStorageContext interface inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + Type type = ExtensionManager.GetImplementations()?.FirstOrDefault(t => !t.GetTypeInfo().IsAbstract); + + if (type == null) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.Data.EntityFramework"); + + logger.LogError("Implementation of ExtCore.Data.Abstractions.IStorageContext not found"); + return; + } + + services.AddScoped(typeof(IStorageContext), type); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/DesignTimeStorageContextFactoryBase.cs b/src/ExtCore.Data.EntityFramework/DesignTimeStorageContextFactoryBase.cs new file mode 100644 index 0000000..3611e98 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/DesignTimeStorageContextFactoryBase.cs @@ -0,0 +1,45 @@ +// Copyright © 2017 David Noreña, Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using ExtCore.Data.Abstractions; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.DependencyInjection; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Implements the interface and represents the factory for providing +/// the registered inside the DI storage context service to the Entity Framework Core tools (such as Migrations). +/// Inherit from this class and call the Initialize method in your web application after the class +/// is configured in order the Entity Framework Core tools (such as Migrations) to work. +/// +/// The storage context type this factory creates. +public abstract class DesignTimeStorageContextFactoryBase : IDesignTimeDbContextFactory where T : StorageContextBase +{ + /// + /// The storage context service from the DI. + /// + public static T StorageContext { get; set; } + + /// + /// Gets the storage context service set by the Initialize method. + /// + /// + /// Storage context service set by the Initialize method. + public T CreateDbContext(string[] args) + { + return DesignTimeStorageContextFactoryBase.StorageContext; + } + + /// + /// Gets the storage context service from the DI and then sets it to the StorageContext property. + /// Call this method inside the Startup.ConfigureServices one after the class + /// is configured. + /// + /// The service provider to get the storage context service. + public static void Initialize(IServiceProvider serviceProvider) + { + DesignTimeStorageContextFactoryBase.StorageContext = serviceProvider.GetService() as T; + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/ExtCore.Data.EntityFramework.csproj b/src/ExtCore.Data.EntityFramework/ExtCore.Data.EntityFramework.csproj new file mode 100644 index 0000000..40f1857 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/ExtCore.Data.EntityFramework.csproj @@ -0,0 +1,21 @@ + + + + + The ExtCore.Data.EntityFramework extension component. Based on the ExtCore framework. + net8.0 + ExtCore.Data.EntityFramework + ExtCore.Data.EntityFramework + + + + + + + + + + + + + diff --git a/src/ExtCore.Data.EntityFramework/Extension.cs b/src/ExtCore.Data.EntityFramework/Extension.cs new file mode 100644 index 0000000..ac2f072 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/Extension.cs @@ -0,0 +1,33 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Overrides the ExtensionBase class and provides the +/// ExtCore.Data.EntityFramework extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.Data.EntityFramework"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/Extensions/StorageContextExtensions.cs b/src/ExtCore.Data.EntityFramework/Extensions/StorageContextExtensions.cs new file mode 100644 index 0000000..81acefd --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/Extensions/StorageContextExtensions.cs @@ -0,0 +1,26 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Contains the extension methods of the IStorageContext interface. +/// +public static class StorageContextExtensions +{ + /// + /// Registers the entities from all the extensions inside the single Entity Framework storage context + /// by finding all the implementations of the IEntityRegistrar interface. + /// + /// The Entity Framework storage context. + /// The Entity Framework model builder. + public static void RegisterEntities(this IStorageContext storageContext, ModelBuilder modelBuilder) + { + foreach (IEntityRegistrar entityRegistrar in ExtensionManager.GetInstances()) + entityRegistrar.RegisterEntities(modelBuilder); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/IEntityRegistrar.cs b/src/ExtCore.Data.EntityFramework/IEntityRegistrar.cs new file mode 100644 index 0000000..f03d4a3 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/IEntityRegistrar.cs @@ -0,0 +1,18 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Describes a mechanism of registering entities inside the Entity Framework storage context. +/// +public interface IEntityRegistrar +{ + /// + /// Registers entities inside the Entity Framework storage context. + /// + /// The Entity Framework model builder. + void RegisterEntities(ModelBuilder modelbuilder); +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/RepositoryBase.cs b/src/ExtCore.Data.EntityFramework/RepositoryBase.cs new file mode 100644 index 0000000..40f9ced --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/RepositoryBase.cs @@ -0,0 +1,28 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Data.Abstractions; +using ExtCore.Data.Entities.Abstractions; +using Microsoft.EntityFrameworkCore; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Implements the IRepository interface and represents default repository behavior. +/// +/// The entity type this repository operates. +public abstract class RepositoryBase : IRepository where TEntity : class, IEntity +{ + protected DbContext storageContext; + protected DbSet dbSet; + + /// + /// Sets the Entity Framework storage context that represents the physical storage to work with. + /// + /// The Entity Framework storage context to set. + public void SetStorageContext(IStorageContext storageContext) + { + this.storageContext = storageContext as DbContext; + this.dbSet = this.storageContext.Set(); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/Storage.cs b/src/ExtCore.Data.EntityFramework/Storage.cs new file mode 100644 index 0000000..6a7ac93 --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/Storage.cs @@ -0,0 +1,62 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Implements the IStorage interface and represents implementation of the +/// Unit of Work design pattern with the mechanism of getting the repositories to work with the underlying +/// Entity Framework storage context and committing the changes made by all the repositories. +/// +public class Storage : IStorage +{ + /// + /// Gets the Entity Framework storage context. + /// + public IStorageContext StorageContext { get; private set; } + + public Storage(IStorageContext storageContext) + { + if (!(storageContext is DbContext)) + throw new ArgumentException("The storageContext object must be an instance of the Microsoft.EntityFrameworkCore.DbContext class."); + + this.StorageContext = storageContext; + } + + /// + /// Gets a repository of the given type. + /// + /// The type parameter to find implementation of. + /// + public TRepository GetRepository() where TRepository : IRepository + { + TRepository repository = ExtensionManager.GetInstance(); + + if (repository != null) + repository.SetStorageContext(this.StorageContext); + + return repository; + } + + /// + /// Commits the changes made by all the repositories. + /// + public int Save() + { + return (this.StorageContext as DbContext).SaveChanges(); + } + + /// + /// Asynchronously commits the changes made by all the repositories. + /// + public async Task SaveAsync() + { + return await (this.StorageContext as DbContext).SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/StorageContextBase.cs b/src/ExtCore.Data.EntityFramework/StorageContextBase.cs new file mode 100644 index 0000000..90a5e1d --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/StorageContextBase.cs @@ -0,0 +1,41 @@ +// Copyright © 2015 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Data.Abstractions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace ExtCore.Data.EntityFramework; + +/// +/// Implements the IStorageContext interface and represents the physical storage +/// with the Entity Framework Core as the ORM. +/// +public abstract class StorageContextBase : DbContext, IStorageContext +{ + /// + /// The connection string that is used to connect to the physical storage. + /// + public string ConnectionString { get; private set; } + + /// + /// The assembly name where migrations are maintained for this context. + /// + public string MigrationsAssembly { get; private set; } + + /// + /// Initializes a new instance of the StorageContext class. + /// + /// The connection string that is used to connect to the physical storage. + public StorageContextBase(IOptions options) + { + this.ConnectionString = options.Value.ConnectionString; + this.MigrationsAssembly = options.Value.MigrationsAssembly; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + this.RegisterEntities(modelBuilder); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/StorageContextOptions.cs b/src/ExtCore.Data.EntityFramework/StorageContextOptions.cs new file mode 100644 index 0000000..cff88ab --- /dev/null +++ b/src/ExtCore.Data.EntityFramework/StorageContextOptions.cs @@ -0,0 +1,20 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace ExtCore.Data.EntityFramework; + +/// +/// Represents Entity Framework storage context options. +/// +public class StorageContextOptions +{ + /// + /// The connection string that is used to connect to the physical storage. + /// + public string ConnectionString { get; set; } + + /// + /// The assembly name where migrations are maintained for this context. + /// + public string MigrationsAssembly { get; set; } +} \ No newline at end of file diff --git a/src/ExtCore.Data.EntityFramework/icon.png b/src/ExtCore.Data.EntityFramework/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data.EntityFramework/icon.png differ diff --git a/src/ExtCore.Data.Models.Abstractions/ExtCore.Data.Models.Abstractions.csproj b/src/ExtCore.Data.Models.Abstractions/ExtCore.Data.Models.Abstractions.csproj deleted file mode 100644 index bb1c880..0000000 --- a/src/ExtCore.Data.Models.Abstractions/ExtCore.Data.Models.Abstractions.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 - ExtCore.Data.Models.Abstractions - ExtCore.Data.Models.Abstractions - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - 1.6.1 - - - diff --git a/src/ExtCore.Data/Actions/AddStorageAction.cs b/src/ExtCore.Data/Actions/AddStorageAction.cs new file mode 100644 index 0000000..883ed36 --- /dev/null +++ b/src/ExtCore.Data/Actions/AddStorageAction.cs @@ -0,0 +1,49 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using ExtCore.Data.Abstractions; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.Data.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// registers found implementation of the IStorage interface inside the DI. +/// +public class AddStorageAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Registers found implementation of the IStorage interface inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + Type type = ExtensionManager.GetImplementations()?.FirstOrDefault(t => !t.GetTypeInfo().IsAbstract); + + if (type == null) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.Data"); + + logger.LogError("Implementation of ExtCore.Data.Abstractions.IStorage not found"); + return; + } + + services.AddScoped(typeof(IStorage), type); + } +} \ No newline at end of file diff --git a/src/ExtCore.Data/DataExtension.cs b/src/ExtCore.Data/DataExtension.cs deleted file mode 100644 index 8030335..0000000 --- a/src/ExtCore.Data/DataExtension.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Reflection; -using ExtCore.Data.Abstractions; -using ExtCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace ExtCore.Data -{ - /// - /// Overrides the ExtensionBase class and defines the ConfigureServices method - /// prioritized action for registering existing implementation of the IStorage interface - /// inside the DI. - /// - public class DataExtension : ExtensionBase - { - /// - /// Defines one prioritized action with priority = 1000 that looks for the implementation of the - /// IStorage interface and registers it inside the DI if found. - /// - public override IEnumerable>> ConfigureServicesActionsByPriorities - { - get - { - return new Dictionary>() - { - [1000] = services => - { - Type type = ExtensionManager.GetImplementation(a => a.FullName.ToLower().Contains("data")); - - if (type == null) - { - this.logger.LogError("Implementation of ExtCore.Data.Abstractions.IStorage not found"); - return; - } - - string connectionString = this.configurationRoot?["Data:DefaultConnection:ConnectionString"]; - - if (string.IsNullOrEmpty(connectionString)) - { - this.logger.LogError("Connection string is not provided"); - return; - } - - type.GetProperty("ConnectionString").SetValue(null, connectionString); - services.AddScoped(typeof(IStorage), type); - } - }; - } - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Data/ExtCore.Data.csproj b/src/ExtCore.Data/ExtCore.Data.csproj index 7cbbd65..dcd0e80 100644 --- a/src/ExtCore.Data/ExtCore.Data.csproj +++ b/src/ExtCore.Data/ExtCore.Data.csproj @@ -1,15 +1,11 @@  + - Part of the ExtCore.Data ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Data extension component. Based on the ExtCore framework. + net8.0 ExtCore.Data ExtCore.Data - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ diff --git a/src/ExtCore.Data/Extension.cs b/src/ExtCore.Data/Extension.cs new file mode 100644 index 0000000..4f244df --- /dev/null +++ b/src/ExtCore.Data/Extension.cs @@ -0,0 +1,32 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.Data; + +/// +/// Overrides the ExtensionBase class and provides the ExtCore.Data extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.Data"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.Data/icon.png b/src/ExtCore.Data/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Data/icon.png differ diff --git a/src/ExtCore.Events/Event.cs b/src/ExtCore.Events/Event.cs index 36ae2b1..6b3e3e4 100644 --- a/src/ExtCore.Events/Event.cs +++ b/src/ExtCore.Events/Event.cs @@ -2,112 +2,112 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using ExtCore.Infrastructure; -namespace ExtCore.Events +namespace ExtCore.Events; + +/// +/// Represents an event that can be broadcasted and handled by the corresponding event handlers +/// specified by the TEventHandler type parameter. The event handlers +/// might be located in this or any other extension and will be resolved automatically by ExtCore. +/// +/// Defines the type of the event handlers that will handle the event. +public static class Event where TEventHandler : IEventHandler { /// - /// Represents an event that can be broadcasted and handled by the corresponding event handlers - /// specified by the TEventHandler type parameter. The event handlers - /// might be located in this or any other extension and will be resolved automatically by ExtCore. + /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. /// - /// Defines the type of the event handlers that will handle the event. - public static class Event where TEventHandler : IEventHandler + /// Resolved event handlers that have handled the event. + public static IEnumerable Broadcast() { - /// - /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. - /// - /// Resolved event handlers that have handled the event. - public static IEnumerable Broadcast() - { - IEnumerable eventHandlers = ExtensionManager.GetInstances(); + IEnumerable eventHandlers = ExtensionManager.GetInstances().OrderBy(eh => eh.Priority); - foreach (TEventHandler eventHandler in eventHandlers) - eventHandler.HandleEvent(); + foreach (TEventHandler eventHandler in eventHandlers) + eventHandler.HandleEvent(); - return eventHandlers; - } + return eventHandlers; } +} +/// +/// Represents an event that can be broadcasted and handled by the corresponding event handlers +/// specified by the TEventHandler type parameter. The event handlers +/// might be located in this or any other extension and will be resolved automatically by ExtCore. +/// +/// Defines the type of the event handlers that will handle the event. +/// Defines the type of the argument that will be passed to the event handlers. +public static class Event where TEventHandler : IEventHandler +{ /// - /// Represents an event that can be broadcasted and handled by the corresponding event handlers - /// specified by the TEventHandler type parameter. The event handlers - /// might be located in this or any other extension and will be resolved automatically by ExtCore. + /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. /// - /// Defines the type of the event handlers that will handle the event. - /// Defines the type of the argument that will be passed to the event handlers. - public static class Event where TEventHandler : IEventHandler + /// The event argument. + /// Resolved event handlers that have handled the event. + public static IEnumerable Broadcast(TEventArgument argument) { - /// - /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. - /// - /// The event argument. - /// Resolved event handlers that have handled the event. - public static IEnumerable Broadcast(TEventArgument argument) - { - IEnumerable eventHandlers = ExtensionManager.GetInstances(); + IEnumerable eventHandlers = ExtensionManager.GetInstances().OrderBy(eh => eh.Priority); - foreach (TEventHandler eventHandler in eventHandlers) - eventHandler.HandleEvent(argument); + foreach (TEventHandler eventHandler in eventHandlers) + eventHandler.HandleEvent(argument); - return eventHandlers; - } + return eventHandlers; } +} +/// +/// Represents an event that can be broadcasted and handled by the corresponding event handlers +/// specified by the TEventHandler type parameter. The event handlers +/// might be located in this or any other extension and will be resolved automatically by ExtCore. +/// +/// +/// Defines the type of the event handlers that will handle the event. +/// Defines the type of the first argument that will be passed to the event handlers. +/// Defines the type of the second argument that will be passed to the event handlers. +public static class Event where TEventHandler : IEventHandler +{ /// - /// Represents an event that can be broadcasted and handled by the corresponding event handlers - /// specified by the TEventHandler type parameter. The event handlers - /// might be located in this or any other extension and will be resolved automatically by ExtCore. + /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. /// - /// - /// Defines the type of the event handlers that will handle the event. - /// Defines the type of the first argument that will be passed to the event handlers. - /// Defines the type of the second argument that will be passed to the event handlers. - public static class Event where TEventHandler : IEventHandler + /// The first event argument. + /// The second event argument. + /// Resolved event handlers that have handled the event. + public static IEnumerable Broadcast(TEventArgument1 argument1, TEventArgument2 argument2) { - /// - /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. - /// - /// The first event argument. - /// The second event argument. - /// Resolved event handlers that have handled the event. - public static IEnumerable Broadcast(TEventArgument1 argument1, TEventArgument2 argument2) - { - IEnumerable eventHandlers = ExtensionManager.GetInstances(); + IEnumerable eventHandlers = ExtensionManager.GetInstances().OrderBy(eh => eh.Priority); - foreach (TEventHandler eventHandler in eventHandlers) - eventHandler.HandleEvent(argument1, argument2); + foreach (TEventHandler eventHandler in eventHandlers) + eventHandler.HandleEvent(argument1, argument2); - return eventHandlers; - } + return eventHandlers; } +} +/// +/// Represents an event that can be broadcasted and handled by the corresponding event handlers +/// specified by the TEventHandler type parameter. The event handlers +/// might be located in this or any other extension and will be resolved automatically by ExtCore. +/// +/// Defines the type of the event handlers that will handle the event. +/// Defines the type of the first argument that will be passed to the event handlers. +/// Defines the type of the second argument that will be passed to the event handlers. +/// Defines the type of the third argument that will be passed to the event handlers. +public static class Event where TEventHandler : IEventHandler +{ /// - /// Represents an event that can be broadcasted and handled by the corresponding event handlers - /// specified by the TEventHandler type parameter. The event handlers - /// might be located in this or any other extension and will be resolved automatically by ExtCore. + /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. /// - /// Defines the type of the event handlers that will handle the event. - /// Defines the type of the first argument that will be passed to the event handlers. - /// Defines the type of the second argument that will be passed to the event handlers. - /// Defines the type of the third argument that will be passed to the event handlers. - public static class Event where TEventHandler : IEventHandler + /// The first event argument. + /// The second event argument. + /// The third event argument. + /// Resolved event handlers that have handled the event. + public static IEnumerable Broadcast(TEventArgument1 argument1, TEventArgument2 argument2, TEventArgument3 argument3) { - /// - /// Broadcasts the event to all the event handlers that are resolved automatically by ExtCore. - /// - /// The first event argument. - /// The second event argument. - /// The third event argument. - /// Resolved event handlers that have handled the event. - public static IEnumerable Broadcast(TEventArgument1 argument1, TEventArgument2 argument2, TEventArgument3 argument3) - { - IEnumerable eventHandlers = ExtensionManager.GetInstances(); + IEnumerable eventHandlers = ExtensionManager.GetInstances().OrderBy(eh => eh.Priority); - foreach (TEventHandler eventHandler in eventHandlers) - eventHandler.HandleEvent(argument1, argument2, argument3); + foreach (TEventHandler eventHandler in eventHandlers) + eventHandler.HandleEvent(argument1, argument2, argument3); - return eventHandlers; - } + return eventHandlers; } } \ No newline at end of file diff --git a/src/ExtCore.Events/ExtCore.Events.csproj b/src/ExtCore.Events/ExtCore.Events.csproj index e041bb3..6a91583 100644 --- a/src/ExtCore.Events/ExtCore.Events.csproj +++ b/src/ExtCore.Events/ExtCore.Events.csproj @@ -1,17 +1,11 @@  + - Part of the ExtCore.Events ExtCore framework extension. - Copyright © 2017 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Events extension component. Based on the ExtCore framework. + net8.0 ExtCore.Events ExtCore.Events - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - 1.6.1 - ExtCore.Events diff --git a/src/ExtCore.Events/Extension.cs b/src/ExtCore.Events/Extension.cs new file mode 100644 index 0000000..4207f4f --- /dev/null +++ b/src/ExtCore.Events/Extension.cs @@ -0,0 +1,32 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.Events; + +/// +/// Overrides the ExtensionBase class and provides the ExtCore.Events extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.Events"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.Events/IEventHandler.cs b/src/ExtCore.Events/IEventHandler.cs index 67bb865..3010b96 100644 --- a/src/ExtCore.Events/IEventHandler.cs +++ b/src/ExtCore.Events/IEventHandler.cs @@ -1,65 +1,88 @@ // Copyright © 2017 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ExtCore.Events +namespace ExtCore.Events; + +/// +/// Describes an event handler that can handle the events broadcasted by the +/// class. +/// +public interface IEventHandler +{ + /// + /// Priority of the event handler. The event handlers of the same event will be executed in the order + /// specified by the priority. + /// + int Priority { get; } + + /// + /// Handles the event. + /// + void HandleEvent(); +} + +/// +/// Describes an event handler that can handle the events broadcasted by the +/// class. +/// +/// Defines the type of the argument that will be passed to the event handler. +public interface IEventHandler { /// - /// Describes an event handler that can handle the events broadcasted by the - /// class. + /// Priority of the event handler. The event handlers of the same event will be executed in the order + /// specified by the priority. /// - public interface IEventHandler - { - /// - /// Handles the event. - /// - void HandleEvent(); - } + int Priority { get; } /// - /// Describes an event handler that can handle the events broadcasted by the - /// class. + /// Handles the event. /// - /// Defines the type of the argument that will be passed to the event handler. - public interface IEventHandler - { - /// - /// Handles the event. - /// - /// The event argument. - void HandleEvent(TEventArgument argument); - } + /// The event argument. + void HandleEvent(TEventArgument argument); +} +/// +/// Describes an event handler that can handle the events broadcasted by the +/// class. +/// +/// Defines the type of the first argument that will be passed to the event handler. +/// Defines the type of the second argument that will be passed to the event handler. +public interface IEventHandler +{ + /// + /// Priority of the event handler. The event handlers of the same event will be executed in the order + /// specified by the priority. + /// + int Priority { get; } + + /// + /// Handles the event. + /// + /// The first event argument. + /// The second event argument. + void HandleEvent(TEventArgument1 argument1, TEventArgument2 argument2); +} + +/// +/// Describes an event handler that can handle the events broadcasted by the +/// class. +/// +/// Defines the type of the first argument that will be passed to the event handler. +/// Defines the type of the second argument that will be passed to the event handler. +/// Defines the type of the first argument that will be passed to the event handler. +public interface IEventHandler +{ /// - /// Describes an event handler that can handle the events broadcasted by the - /// class. + /// Priority of the event handler. The event handlers of the same event will be executed in the order + /// specified by the priority. /// - /// Defines the type of the first argument that will be passed to the event handler. - /// Defines the type of the second argument that will be passed to the event handler. - public interface IEventHandler - { - /// - /// Handles the event. - /// - /// The first event argument. - /// The second event argument. - void HandleEvent(TEventArgument1 argument1, TEventArgument2 argument2); - } + int Priority { get; } /// - /// Describes an event handler that can handle the events broadcasted by the - /// class. + /// Handles the event. /// - /// Defines the type of the first argument that will be passed to the event handler. - /// Defines the type of the second argument that will be passed to the event handler. - /// Defines the type of the first argument that will be passed to the event handler. - public interface IEventHandler - { - /// - /// Handles the event. - /// - /// The first event argument. - /// The second event argument. - /// The third event argument. - void HandleEvent(TEventArgument1 argument1, TEventArgument2 argument2, TEventArgument3 argument3); - } + /// The first event argument. + /// The second event argument. + /// The third event argument. + void HandleEvent(TEventArgument1 argument1, TEventArgument2 argument2, TEventArgument3 argument3); } \ No newline at end of file diff --git a/src/ExtCore.Events/icon.png b/src/ExtCore.Events/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Events/icon.png differ diff --git a/src/ExtCore.FileStorage.Abstractions/Exceptions/AccessDeniedException.cs b/src/ExtCore.FileStorage.Abstractions/Exceptions/AccessDeniedException.cs new file mode 100644 index 0000000..389e6b7 --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/Exceptions/AccessDeniedException.cs @@ -0,0 +1,16 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace ExtCore.FileStorage; + +/// +/// Represents an access denied file storage exception. +/// +public class AccessDeniedException : FileStorageException +{ + public AccessDeniedException() : base() { } + public AccessDeniedException(string message) : base(message) { } + public AccessDeniedException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/Exceptions/DirectoryNotFoundException.cs b/src/ExtCore.FileStorage.Abstractions/Exceptions/DirectoryNotFoundException.cs new file mode 100644 index 0000000..0584731 --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/Exceptions/DirectoryNotFoundException.cs @@ -0,0 +1,16 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace ExtCore.FileStorage; + +/// +/// Represents a directory not found file storage exception. +/// +public class DirectoryNotFoundException : FileStorageException +{ + public DirectoryNotFoundException() : base() { } + public DirectoryNotFoundException(string message) : base(message) { } + public DirectoryNotFoundException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/Exceptions/FileNotFoundException.cs b/src/ExtCore.FileStorage.Abstractions/Exceptions/FileNotFoundException.cs new file mode 100644 index 0000000..f4d1dbc --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/Exceptions/FileNotFoundException.cs @@ -0,0 +1,16 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace ExtCore.FileStorage; + +/// +/// Represents a directory not found file storage exception. +/// +public class FileNotFoundException : FileStorageException +{ + public FileNotFoundException() : base() { } + public FileNotFoundException(string message) : base(message) { } + public FileNotFoundException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/Exceptions/FileStorageException.cs b/src/ExtCore.FileStorage.Abstractions/Exceptions/FileStorageException.cs new file mode 100644 index 0000000..11d5c24 --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/Exceptions/FileStorageException.cs @@ -0,0 +1,16 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace ExtCore.FileStorage; + +/// +/// Represents a generic file storage exception. +/// +public class FileStorageException : Exception +{ + public FileStorageException() : base() { } + public FileStorageException(string message) : base(message) { } + public FileStorageException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/Exceptions/PathTooLongException.cs b/src/ExtCore.FileStorage.Abstractions/Exceptions/PathTooLongException.cs new file mode 100644 index 0000000..9d6e1aa --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/Exceptions/PathTooLongException.cs @@ -0,0 +1,16 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace ExtCore.FileStorage; + +/// +/// Represents a path too long file storage exception. +/// +public class PathTooLongException : FileStorageException +{ + public PathTooLongException() : base() { } + public PathTooLongException(string message) : base(message) { } + public PathTooLongException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/ExtCore.FileStorage.Abstractions.csproj b/src/ExtCore.FileStorage.Abstractions/ExtCore.FileStorage.Abstractions.csproj new file mode 100644 index 0000000..84dc73d --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/ExtCore.FileStorage.Abstractions.csproj @@ -0,0 +1,11 @@ + + + + + The ExtCore.FileStorage extension component. Based on the ExtCore framework. + netstandard2.0 + ExtCore.FileStorage.Abstractions + ExtCore.FileStorage.Abstractions + + + diff --git a/src/ExtCore.FileStorage.Abstractions/FileStorageOptions.cs b/src/ExtCore.FileStorage.Abstractions/FileStorageOptions.cs new file mode 100644 index 0000000..47c466d --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/FileStorageOptions.cs @@ -0,0 +1,30 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace ExtCore.FileStorage; + +/// +/// Represents generic file storage options. +/// +public class FileStorageOptions +{ + /// + /// The origin that is used to connect to the file storage. Might be used to provide an API base URL. + /// + public string Origin { get; set; } + + /// + /// The identifier that is used to connect to the file storage. Might be used to provide a username or an application identifier. + /// + public string Identifier { get; set; } + + /// + /// The secret that is used to connect to the file storage. Might be used to provide a password, an application secret, or an API key. + /// + public string Secret { get; set; } + + /// + /// The root path that is used by the file storage. + /// + public string RootPath { get; set; } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/IDirectoryProxy.cs b/src/ExtCore.FileStorage.Abstractions/IDirectoryProxy.cs new file mode 100644 index 0000000..f504af5 --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/IDirectoryProxy.cs @@ -0,0 +1,76 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ExtCore.FileStorage.Abstractions; + +/// +/// Describes a generic directory proxy to manipulate an underlying directory with a specified relative path. +/// +public interface IDirectoryProxy +{ + /// + /// The path of the underlying directory relatively to the root one. + /// + string RelativePath { get; } + + /// + /// Checks if the underlying directory exists. + /// + /// Returns a flag indicating if the underlying directory exists. + Task ExistsAsync(); + + /// + /// Creates the underlying directory. + /// + /// + /// + /// + /// + Task CreateAsync(); + + /// + /// Moves the underlying directory. + /// + /// + /// + /// + /// + /// + /// + /// + Task MoveAsync(string destinationRelativePath); + + /// + /// Deletes the underlying directory. + /// + /// Pass true to remove all the underlying directory content recursively; otherwise false. + /// + /// + /// + /// + Task DeleteAsync(bool recursive); + + /// + /// Gets the directory proxies for the directories inside the underlying one. + /// + /// The directory proxies for the directories inside the underlying one + /// + /// + /// + /// + Task> GetDirectoryProxiesAsync(); + + /// + /// Gets the file proxies for the files inside the underlying one. + /// + /// The file proxies for the files inside the underlying directory. + /// + /// + /// + /// + Task> GetFileProxiesAsync(); +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/IFileProxy.cs b/src/ExtCore.FileStorage.Abstractions/IFileProxy.cs new file mode 100644 index 0000000..c006c7f --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/IFileProxy.cs @@ -0,0 +1,106 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; + +namespace ExtCore.FileStorage.Abstractions; + +/// +/// Describes a generic file proxy to manipulate an underlying file with a specified relative path and a filename. +/// +public interface IFileProxy +{ + /// + /// The path of the underlying file relatively to the root one. + /// + string RelativePath { get; } + + /// + /// The filename of the underlying file. + /// + string Filename { get; } + + /// + /// Checks if the underlying file exists. + /// + /// Returns a flag indicating if the underlying file exists. + Task ExistsAsync(); + + /// + /// Reads content of the underlying file as a stream. + /// + /// Content of the underlying file as a stream. + /// + /// + /// + /// + /// + Task ReadStreamAsync(); + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + Task ReadBytesAsync(); + + /// + /// Reads content of the underlying file as a text string. + /// + /// Content of the underlying file as a text string. + /// + /// + /// + /// + /// + Task ReadTextAsync(); + + /// + /// Writes content to the underlying file as a stream. + /// + /// Content to write to the underlying file as a stream. + /// + /// + /// + /// + /// + Task WriteStreamAsync(Stream inputStream); + + /// + /// Writes content to the underlying file as a byte array. + /// + /// Content to write to the underlying file as a byte array. + /// + /// + /// + /// + /// + Task WriteBytesAsync(byte[] bytes); + + /// + /// Writes content to the underlying file as a text string. + /// + /// Content to write to the underlying file as a text string. + /// + /// + /// + /// + /// + Task WriteTextAsync(string text); + + /// + /// Deletes the underlying file. + /// + /// + /// + /// + /// + /// + Task DeleteAsync(); +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/IFileStorage.cs b/src/ExtCore.FileStorage.Abstractions/IFileStorage.cs new file mode 100644 index 0000000..722958a --- /dev/null +++ b/src/ExtCore.FileStorage.Abstractions/IFileStorage.cs @@ -0,0 +1,25 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace ExtCore.FileStorage.Abstractions; + +/// +/// Describes a generic file storage that allows to manipulate directories and files via proxies. +/// +public interface IFileStorage +{ + /// + /// Creates a directory proxy which allows to manipulate an underlying directory with a specified relative path. + /// + /// The path of the underlying directory relatively to the root one. + /// Created directory proxy. + IDirectoryProxy CreateDirectoryProxy(string relativePath); + + /// + /// Creates a file proxy which allows to manipulate an underlying file with a specified relative path and a filename. + /// + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// Created file proxy. + IFileProxy CreateFileProxy(string relativePath, string filename); +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Abstractions/icon.png b/src/ExtCore.FileStorage.Abstractions/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.FileStorage.Abstractions/icon.png differ diff --git a/src/ExtCore.FileStorage.Azure/DirectoryProxy.cs b/src/ExtCore.FileStorage.Azure/DirectoryProxy.cs new file mode 100644 index 0000000..d6b474b --- /dev/null +++ b/src/ExtCore.FileStorage.Azure/DirectoryProxy.cs @@ -0,0 +1,224 @@ +// Copyright © 2022 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Azure; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.Azure; + +/// +/// Implements the IDirectoryProxy interface and represents a directory in a Azure Storage account. +/// +public class DirectoryProxy : IDirectoryProxy +{ + private readonly string connectionString; + private readonly string rootPath; + private readonly string path; + private readonly string containerName; + private readonly string prefix; + + /// + /// The path of the underlying directory relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// Initializes a new instance of the DirectoryProxy class. + /// + /// The Azure Storage account connection string. + /// The root path of the underlying directory's relative one. + /// The path of the underlying directory relatively to the root one. + /// + /// + public DirectoryProxy(string connectionString, string rootPath, string relativePath) + { + if (connectionString == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: connectionString."); + + if (connectionString == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: connectionString.", default(Exception)); + + this.connectionString = connectionString; + this.rootPath = RelativeUrl.Combine(rootPath); + this.RelativePath = RelativeUrl.Combine(relativePath); + this.path = RelativeUrl.Combine(this.rootPath, this.RelativePath); + + string[] urlSegments = path.Split('/'); + + this.containerName = urlSegments.First(); + this.prefix = string.Join("/", urlSegments.Skip(1)); + } + + /// + /// Checks if the underlying directory exists. + /// + /// Returns a flag indicating if the underlying directory exists. + public async Task ExistsAsync() + { + try + { + BlobContainerClient blobContainerClient = this.GetBlobContainerClient(); + IAsyncEnumerable> pages = blobContainerClient.GetBlobsByHierarchyAsync(prefix: this.prefix).AsPages(); + + await foreach (Page page in pages) + return page.Values.Count != 0; + } + + catch { } + + return false; + } + + /// + /// Creates the underlying directory. + /// + /// + /// + /// + /// + public async Task CreateAsync() + { + throw new NotSupportedException(); + } + + /// + /// Moves the underlying directory. + /// + /// + /// + /// + /// + /// + /// + /// + public async Task MoveAsync(string destinationRelativePath) + { + throw new NotSupportedException(); + } + + /// + /// Deletes the underlying directory. + /// + /// Pass true to remove all the underlying directory content recursively; otherwise false. + /// + /// + /// + /// + public async Task DeleteAsync(bool recursive) + { + try + { + BlobContainerClient blobContainerClient = this.GetBlobContainerClient(); + + if (string.IsNullOrEmpty(this.prefix)) + { + await blobContainerClient.DeleteAsync(); + return; + } + + string prefix = this.prefix + "/"; + IAsyncEnumerable> pages = blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: recursive ? null : "/").AsPages(); + + await foreach (Page page in pages) + { + foreach (BlobHierarchyItem blobItem in page.Values) + { + if (blobItem.IsBlob) + { + BlobClient blobClient = await this.GetBlobClient(blobItem.Blob); + + await blobClient.DeleteAsync(); + } + } + } + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + /// + /// Gets the directory proxies for the directories inside the underlying one. + /// + /// The directory proxies for the directories inside the underlying one + /// + /// + /// + /// + public async Task> GetDirectoryProxiesAsync() + { + try + { + IList directoryProxies = new List(); + BlobContainerClient blobContainerClient = this.GetBlobContainerClient(); + string prefix = string.IsNullOrEmpty(this.prefix) ? null : this.prefix + "/"; + IAsyncEnumerable> pages = blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/").AsPages(); + + await foreach (Page page in pages) + foreach (BlobHierarchyItem blobItem in page.Values) + if (blobItem.IsPrefix) + directoryProxies.Add(new DirectoryProxy(this.connectionString, this.rootPath, RelativeUrl.Combine(this.RelativePath, RelativeUrl.Combine(blobItem.Prefix).Split('/').Last()))); + + return directoryProxies; + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + /// + /// Gets the file proxies for the files inside the underlying one. + /// + /// The file proxies for the files inside the underlying directory. + /// + /// + /// + /// + public async Task> GetFileProxiesAsync() + { + try + { + IList fileProxies = new List(); + BlobContainerClient blobContainerClient = this.GetBlobContainerClient(); + string prefix = string.IsNullOrEmpty(this.prefix) ? null : this.prefix + "/"; + IAsyncEnumerable> pages = blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/").AsPages(); + + await foreach (Page page in pages) + foreach (BlobHierarchyItem blobItem in page.Values) + if (blobItem.IsBlob) + fileProxies.Add(new FileProxy(this.connectionString, this.rootPath, this.RelativePath, blobItem.Blob.Name.Split('/').Last())); + + return fileProxies; + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + private BlobContainerClient GetBlobContainerClient() + { + BlobServiceClient blobServiceClient = new BlobServiceClient(this.connectionString); + + return blobServiceClient.GetBlobContainerClient(this.containerName); + } + + private async Task GetBlobClient(BlobItem blobItem) + { + BlobContainerClient blobContainerClient = this.GetBlobContainerClient(); + + await blobContainerClient.CreateIfNotExistsAsync(); + return blobContainerClient.GetBlobClient(blobItem.Name); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Azure/ExtCore.FileStorage.Azure.csproj b/src/ExtCore.FileStorage.Azure/ExtCore.FileStorage.Azure.csproj new file mode 100644 index 0000000..07eb781 --- /dev/null +++ b/src/ExtCore.FileStorage.Azure/ExtCore.FileStorage.Azure.csproj @@ -0,0 +1,21 @@ + + + + + The ExtCore.FileStorage extension component. Based on the ExtCore framework. + netstandard2.0 + ExtCore.FileStorage.Azure + ExtCore.FileStorage.Azure + + + + + + + + + + + + + diff --git a/src/ExtCore.FileStorage.Azure/FileProxy.cs b/src/ExtCore.FileStorage.Azure/FileProxy.cs new file mode 100644 index 0000000..9fced27 --- /dev/null +++ b/src/ExtCore.FileStorage.Azure/FileProxy.cs @@ -0,0 +1,267 @@ +// Copyright © 2022 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.Azure; + +/// +/// Implements the IDirectoryProxy interface and represents a file in a Azure Storage account. +/// +public class FileProxy : IFileProxy +{ + private readonly string connectionString; + private readonly string filepath; + private readonly string containerName; + private readonly string blobName; + + /// + /// The path of the underlying file relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// The filename of the underlying file. + /// + public string Filename { get; private set; } + + /// + /// Initializes a new instance of the FileProxy class. + /// + /// The Azure Storage account connection string. + /// The root path of the underlying file's relative one. + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// + /// + public FileProxy(string connectionString, string rootPath, string relativePath, string filename) + { + if (connectionString == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: connectionString."); + + if (connectionString == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: connectionString.", default(Exception)); + + if (filename == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: filename."); + + if (filename == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: filename.", default(Exception)); + + this.connectionString = connectionString; + this.RelativePath = RelativeUrl.Combine(relativePath); + this.Filename = filename; + this.filepath = RelativeUrl.Combine(rootPath, this.RelativePath, this.Filename); + + string[] urlSegments = filepath.Split('/'); + + this.containerName = urlSegments.First(); + this.blobName = string.Join("/", urlSegments.Skip(1)); + } + + /// + /// Checks if the underlying file exists. + /// + /// Returns a flag indicating if the underlying file exists. + public async Task ExistsAsync() + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + + return await blobClient.ExistsAsync(); + } + + catch + { + return false; + } + } + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task ReadStreamAsync() + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + MemoryStream stream = new MemoryStream(); + + await blobClient.DownloadToAsync(stream); + stream.Position = 0; + return stream; + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task ReadBytesAsync() + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + BlobDownloadResult downloadResult = await blobClient.DownloadContentAsync(); + + return downloadResult.Content.ToArray(); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Reads content of the underlying file as a text string. + /// + /// Content of the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task ReadTextAsync() + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + BlobDownloadResult downloadResult = await blobClient.DownloadContentAsync(); + + return downloadResult.Content.ToString(); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a stream. + /// + /// Content to write to the underlying file as a stream. + /// + /// + /// + /// + /// + public async Task WriteStreamAsync(Stream inputStream) + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + + await blobClient.UploadAsync(inputStream); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a byte array. + /// + /// Content to write to the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task WriteBytesAsync(byte[] bytes) + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + + await blobClient.UploadAsync(BinaryData.FromBytes(bytes)); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a text string. + /// + /// Content to write to the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task WriteTextAsync(string text) + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + + await blobClient.UploadAsync(BinaryData.FromString(text)); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Deletes the underlying file. + /// + /// + /// + /// + /// + /// + public async Task DeleteAsync() + { + try + { + BlobClient blobClient = await this.GetBlobClient(); + + await blobClient.DeleteAsync(); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + private async Task GetBlobClient() + { + BlobServiceClient blobServiceClient = new BlobServiceClient(this.connectionString); + BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient(this.containerName); + + await blobContainerClient.CreateIfNotExistsAsync(); + return blobContainerClient.GetBlobClient(this.blobName); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Azure/FileStorage.cs b/src/ExtCore.FileStorage.Azure/FileStorage.cs new file mode 100644 index 0000000..b5a9b2b --- /dev/null +++ b/src/ExtCore.FileStorage.Azure/FileStorage.cs @@ -0,0 +1,47 @@ +// Copyright © 2022 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.FileStorage.Abstractions; +using Microsoft.Extensions.Options; + +namespace ExtCore.FileStorage.Azure; + +/// +/// Implements the IFileStorage interface and represents a file storage in a Azure Storage account. +/// +public class FileStorage : IFileStorage +{ + private readonly string secret; + private readonly string rootPath; + + /// + /// Initializes a new instance of the FileStorage class. + /// + /// The options that are used to configure the file storage root path. + public FileStorage(IOptions options) + { + this.secret = options.Value.Secret; + this.rootPath = options.Value.RootPath; + } + + /// + /// Creates a directory proxy which allows to manipulate an underlying directory with a specified relative path. + /// + /// The path of the underlying directory relatively to the root one. + /// Created directory proxy. + public IDirectoryProxy CreateDirectoryProxy(string relativePath) + { + return new DirectoryProxy(this.secret, this.rootPath, relativePath); + } + + /// + /// Creates a file proxy which allows to manipulate an underlying file with a specified relative path and a filename. + /// + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// Created file proxy. + public IFileProxy CreateFileProxy(string relativePath, string filename) + { + return new FileProxy(this.secret, this.rootPath, relativePath, filename); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Azure/RelativeUrl.cs b/src/ExtCore.FileStorage.Azure/RelativeUrl.cs new file mode 100644 index 0000000..539139f --- /dev/null +++ b/src/ExtCore.FileStorage.Azure/RelativeUrl.cs @@ -0,0 +1,20 @@ +// Copyright © 2022 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; + +namespace ExtCore.FileStorage.Azure; + +public static class RelativeUrl +{ + public static string Combine(params string[] segments) + { + return string.Join( + "/", + segments.Where(s => !string.IsNullOrEmpty(s)).Select( + s => string.Join("/", s.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries)) + ).Where(s => !string.IsNullOrEmpty(s)) + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Azure/icon.png b/src/ExtCore.FileStorage.Azure/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.FileStorage.Azure/icon.png differ diff --git a/src/ExtCore.FileStorage.Dropbox/DirectoryProxy.cs b/src/ExtCore.FileStorage.Dropbox/DirectoryProxy.cs new file mode 100644 index 0000000..f18de8c --- /dev/null +++ b/src/ExtCore.FileStorage.Dropbox/DirectoryProxy.cs @@ -0,0 +1,243 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Dropbox.Api; +using Dropbox.Api.Files; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.Dropbox; + +/// +/// Implements the IDirectoryProxy interface and represents a directory in a Dropbox account. +/// +public class DirectoryProxy : IDirectoryProxy +{ + private readonly string accessToken; + private readonly string rootPath; + private readonly string path; + + /// + /// The path of the underlying directory relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// Initializes a new instance of the DirectoryProxy class. + /// + /// The Dropbox's account access token. + /// The root path of the underlying directory's relative one. + /// The path of the underlying directory relatively to the root one. + /// + /// + public DirectoryProxy(string accessToken, string rootPath, string relativePath) + { + if (accessToken == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: accessToken."); + + if (accessToken == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: accessToken.", default(Exception)); + + this.accessToken = accessToken; + this.rootPath = RelativeUrl.Combine(rootPath); + this.RelativePath = RelativeUrl.Combine(relativePath); + this.path = RelativeUrl.Combine(this.rootPath, this.RelativePath); + + if (string.Equals(this.path, "/")) + this.path = string.Empty; + } + + /// + /// Checks if the underlying directory exists. + /// + /// Returns a flag indicating if the underlying directory exists. + public async Task ExistsAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + { + Metadata metadata = await dropboxClient.Files.GetMetadataAsync(this.path); + + return metadata.IsFolder; + } + } + + catch { return false; } + } + + /// + /// Creates the underlying directory. + /// + /// + /// + /// + /// + public async Task CreateAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + await dropboxClient.Files.CreateFolderV2Async(this.path); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + /// + /// Moves the underlying directory. + /// + /// + /// + /// + /// + /// + /// + /// + public async Task MoveAsync(string destinationRelativePath) + { + if (destinationRelativePath == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: destinationRelativePath."); + + if (destinationRelativePath == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: destinationRelativePath.", default(Exception)); + + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + await dropboxClient.Files.MoveV2Async(this.path, RelativeUrl.Combine(this.rootPath, destinationRelativePath)); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsFromLookup) + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + /// + /// Deletes the underlying directory. + /// + /// Pass true to remove all the underlying directory content recursively; otherwise false. + /// + /// + /// + /// + public async Task DeleteAsync(bool recursive) + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + await dropboxClient.Files.DeleteV2Async(this.path); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPathLookup) + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + + /// + /// Gets the directory proxies for the directories inside the underlying one. + /// + /// The directory proxies for the directories inside the underlying one + /// + /// + /// + /// + public async Task> GetDirectoryProxiesAsync() + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + { + IList directoryProxies = new List(); + + try + { + foreach (Metadata metadata in (await dropboxClient.Files.ListFolderAsync(this.path)).Entries.Where(m => m.IsFolder)) + directoryProxies.Add(new DirectoryProxy(this.accessToken, this.rootPath, metadata.PathDisplay.Substring(this.rootPath.Length))); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + return directoryProxies; + } + } + + /// + /// Gets the file proxies for the files inside the underlying one. + /// + /// The file proxies for the files inside the underlying directory. + /// + /// + /// + /// + public async Task> GetFileProxiesAsync() + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + { + IList fileProxies = new List(); + + try + { + foreach (Metadata metadata in (await dropboxClient.Files.ListFolderAsync(this.path)).Entries.Where(m => m.IsFile)) + { + string relativePath = metadata.PathDisplay; + + relativePath = relativePath.Substring(this.rootPath.Length); + + if (relativePath.Contains("/")) + relativePath = relativePath.Remove(relativePath.LastIndexOf("/")); + + fileProxies.Add(new FileProxy(this.accessToken, this.rootPath, relativePath, metadata.Name)); + } + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + + return fileProxies; + } + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Dropbox/ExtCore.FileStorage.Dropbox.csproj b/src/ExtCore.FileStorage.Dropbox/ExtCore.FileStorage.Dropbox.csproj new file mode 100644 index 0000000..46dcec9 --- /dev/null +++ b/src/ExtCore.FileStorage.Dropbox/ExtCore.FileStorage.Dropbox.csproj @@ -0,0 +1,20 @@ + + + + + The ExtCore.FileStorage extension component. Based on the ExtCore framework. + netstandard2.0 + ExtCore.FileStorage.Dropbox + ExtCore.FileStorage.Dropbox + + + + + + + + + + + + diff --git a/src/ExtCore.FileStorage.Dropbox/FileProxy.cs b/src/ExtCore.FileStorage.Dropbox/FileProxy.cs new file mode 100644 index 0000000..e4fe2d1 --- /dev/null +++ b/src/ExtCore.FileStorage.Dropbox/FileProxy.cs @@ -0,0 +1,276 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Dropbox.Api; +using Dropbox.Api.Files; +using Dropbox.Api.Stone; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.Dropbox; + +/// +/// Implements the IDirectoryProxy interface and represents a file in a Dropbox account. +/// +public class FileProxy : IFileProxy +{ + private readonly string accessToken; + private readonly string rootPath; + private readonly string filepath; + + /// + /// The path of the underlying file relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// The filename of the underlying file. + /// + public string Filename { get; private set; } + + /// + /// Initializes a new instance of the FileProxy class. + /// + /// The Dropbox's account access token. + /// The root path of the underlying file's relative one. + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// + /// + public FileProxy(string accessToken, string rootPath, string relativePath, string filename) + { + if (accessToken == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: accessToken."); + + if (accessToken == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: accessToken.", default(Exception)); + + if (filename == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: filename."); + + if (filename == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: filename.", default(Exception)); + + this.accessToken = accessToken; + this.rootPath = RelativeUrl.Combine(rootPath); + this.RelativePath = RelativeUrl.Combine(relativePath); + this.Filename = filename; + this.filepath = RelativeUrl.Combine(this.rootPath, this.RelativePath, this.Filename); + } + + /// + /// Checks if the underlying file exists. + /// + /// Returns a flag indicating if the underlying file exists. + public async Task ExistsAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + { + Metadata metadata = await dropboxClient.Files.GetMetadataAsync(this.filepath); + + return metadata.IsFile; + } + } + + catch { return false; } + } + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task ReadStreamAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + using (IDownloadResponse response = await dropboxClient.Files.DownloadAsync(this.filepath)) + return await response.GetContentAsStreamAsync(); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task ReadBytesAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + using (IDownloadResponse response = await dropboxClient.Files.DownloadAsync(this.filepath)) + return await response.GetContentAsByteArrayAsync(); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Reads content of the underlying file as a text string. + /// + /// Content of the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task ReadTextAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + using (IDownloadResponse response = await dropboxClient.Files.DownloadAsync(this.filepath)) + return await response.GetContentAsStringAsync(); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a stream. + /// + /// Content to write to the underlying file as a stream. + /// + /// + /// + /// + /// + public async Task WriteStreamAsync(Stream inputStream) + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + await dropboxClient.Files.UploadAsync(this.filepath, WriteMode.Overwrite.Instance, body: inputStream); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a byte array. + /// + /// Content to write to the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task WriteBytesAsync(byte[] bytes) + { + try + { + using (MemoryStream inputStream = new MemoryStream(bytes)) + await this.WriteStreamAsync(inputStream); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Writes content to the underlying file as a text string. + /// + /// Content to write to the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task WriteTextAsync(string text) + { + try + { + await this.WriteBytesAsync(Encoding.UTF8.GetBytes(text)); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } + + /// + /// Deletes the underlying file. + /// + /// + /// + /// + /// + /// + public async Task DeleteAsync() + { + try + { + using (DropboxClient dropboxClient = new DropboxClient(this.accessToken)) + await dropboxClient.Files.DeleteV2Async(this.filepath); + } + + catch (ApiException e) + { + if (e.ErrorResponse.IsPath) + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Dropbox/FileStorage.cs b/src/ExtCore.FileStorage.Dropbox/FileStorage.cs new file mode 100644 index 0000000..ab5f7a2 --- /dev/null +++ b/src/ExtCore.FileStorage.Dropbox/FileStorage.cs @@ -0,0 +1,47 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.FileStorage.Abstractions; +using Microsoft.Extensions.Options; + +namespace ExtCore.FileStorage.Dropbox; + +/// +/// Implements the IFileStorage interface and represents a file storage in a Dropbox account. +/// +public class FileStorage : IFileStorage +{ + private readonly string secret; + private readonly string rootPath; + + /// + /// Initializes a new instance of the FileStorage class. + /// + /// The options that are used to configure the file storage root path. + public FileStorage(IOptions options) + { + this.secret = options.Value.Secret; + this.rootPath = options.Value.RootPath; + } + + /// + /// Creates a directory proxy which allows to manipulate an underlying directory with a specified relative path. + /// + /// The path of the underlying directory relatively to the root one. + /// Created directory proxy. + public IDirectoryProxy CreateDirectoryProxy(string relativePath) + { + return new DirectoryProxy(this.secret, this.rootPath, relativePath); + } + + /// + /// Creates a file proxy which allows to manipulate an underlying file with a specified relative path and a filename. + /// + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// Created file proxy. + public IFileProxy CreateFileProxy(string relativePath, string filename) + { + return new FileProxy(this.secret, this.rootPath, relativePath, filename); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Dropbox/RelativeUrl.cs b/src/ExtCore.FileStorage.Dropbox/RelativeUrl.cs new file mode 100644 index 0000000..298b9a0 --- /dev/null +++ b/src/ExtCore.FileStorage.Dropbox/RelativeUrl.cs @@ -0,0 +1,20 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; + +namespace ExtCore.FileStorage.Dropbox; + +public static class RelativeUrl +{ + public static string Combine(params string[] segments) + { + return "/" + string.Join( + "/", + segments.Where(s => !string.IsNullOrEmpty(s)).Select( + s => string.Join("/", s.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries)) + ).Where(s => !string.IsNullOrEmpty(s)) + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.Dropbox/icon.png b/src/ExtCore.FileStorage.Dropbox/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.FileStorage.Dropbox/icon.png differ diff --git a/src/ExtCore.FileStorage.FileSystem/AbsolutePath.cs b/src/ExtCore.FileStorage.FileSystem/AbsolutePath.cs new file mode 100644 index 0000000..1c27920 --- /dev/null +++ b/src/ExtCore.FileStorage.FileSystem/AbsolutePath.cs @@ -0,0 +1,21 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; + +namespace ExtCore.FileStorage.FileSystem; + +public static class AbsolutePath +{ + public static string Combine(params string[] segments) + { + return string.Join( + Path.DirectorySeparatorChar.ToString(), + segments.Where(s => !string.IsNullOrEmpty(s)).Select( + s => string.Join(Path.DirectorySeparatorChar.ToString(), s.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries)) + ).Where(s => !string.IsNullOrEmpty(s)) + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.FileSystem/DirectoryProxy.cs b/src/ExtCore.FileStorage.FileSystem/DirectoryProxy.cs new file mode 100644 index 0000000..d7f1dce --- /dev/null +++ b/src/ExtCore.FileStorage.FileSystem/DirectoryProxy.cs @@ -0,0 +1,263 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.FileSystem; + +/// +/// Implements the IDirectoryProxy interface and represents a directory in a file system. +/// +public class DirectoryProxy : IDirectoryProxy +{ + private readonly string rootPath; + private readonly string path; + + /// + /// The path of the underlying directory relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// Initializes a new instance of the DirectoryProxy class. + /// + /// The root path of the underlying directory's relative one. + /// The path of the underlying directory relatively to the root one. + public DirectoryProxy(string rootPath, string relativePath) + { + this.rootPath = AbsolutePath.Combine(rootPath); + this.RelativePath = AbsolutePath.Combine(relativePath); + this.path = AbsolutePath.Combine(this.rootPath, this.RelativePath); + } + + /// + /// Checks if the underlying directory exists. + /// + /// Returns a flag indicating if the underlying directory exists. + public async Task ExistsAsync() + { + return await Task.Factory.StartNew(() => Directory.Exists(this.path)); + } + + /// + /// Creates the underlying directory. + /// + /// + /// + /// + /// + public async Task CreateAsync() + { + await Task.Factory.StartNew(() => + { + try + { + Directory.CreateDirectory(this.path); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + }); + } + + /// + /// Moves the underlying directory. + /// + /// + /// + /// + /// + /// + /// + /// + public async Task MoveAsync(string destinationRelativePath) + { + if (destinationRelativePath == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: destinationRelativePath."); + + if (destinationRelativePath == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: destinationRelativePath.", default(Exception)); + + await Task.Factory.StartNew(() => + { + try + { + Directory.Move(this.path, this.rootPath + destinationRelativePath); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + }); + } + + /// + /// Deletes the underlying directory. + /// + /// Pass true to remove all the underlying directory content recursively; otherwise false. + /// + /// + /// + /// + public async Task DeleteAsync(bool recursive) + { + await Task.Factory.StartNew(() => + { + try + { + Directory.Delete(this.path, recursive); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + }); + } + + /// + /// Gets the directory proxies for the directories inside the underlying one. + /// + /// The directory proxies for the directories inside the underlying one + /// + /// + /// + /// + public async Task> GetDirectoryProxiesAsync() + { + return await Task>.Factory.StartNew( + () => + { + try + { + return Directory.GetDirectories(this.path).Select( + d => new DirectoryProxy(this.rootPath, d.Substring(this.rootPath.Length)) + ); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + ); + } + + /// + /// Gets the file proxies for the files inside the underlying one. + /// + /// The file proxies for the files inside the underlying directory. + /// + /// + /// + /// + public async Task> GetFileProxiesAsync() + { + return await Task>.Factory.StartNew( + () => + { + try + { + return Directory.GetFiles(this.path).Select( + f => + { + string relativePath = f; + + relativePath = relativePath.Substring(this.rootPath.Length); + relativePath = relativePath.Remove(relativePath.LastIndexOf(Path.DirectorySeparatorChar)); + return new FileProxy(this.rootPath, relativePath, f.Substring(f.LastIndexOf(Path.DirectorySeparatorChar) + 1)); + } + ); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.path}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.path}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.path}\". See inner exception for details.", e); + } + } + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.FileSystem/ExtCore.FileStorage.FileSystem.csproj b/src/ExtCore.FileStorage.FileSystem/ExtCore.FileStorage.FileSystem.csproj new file mode 100644 index 0000000..a5e0087 --- /dev/null +++ b/src/ExtCore.FileStorage.FileSystem/ExtCore.FileStorage.FileSystem.csproj @@ -0,0 +1,19 @@ + + + + + The ExtCore.FileStorage extension component. Based on the ExtCore framework. + netstandard2.0 + ExtCore.FileStorage.FileSystem + ExtCore.FileStorage.FileSystem + + + + + + + + + + + diff --git a/src/ExtCore.FileStorage.FileSystem/FileProxy.cs b/src/ExtCore.FileStorage.FileSystem/FileProxy.cs new file mode 100644 index 0000000..1a44a5d --- /dev/null +++ b/src/ExtCore.FileStorage.FileSystem/FileProxy.cs @@ -0,0 +1,387 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Security; +using System.Threading.Tasks; +using ExtCore.FileStorage.Abstractions; + +namespace ExtCore.FileStorage.FileSystem; + +/// +/// Implements the IDirectoryProxy interface and represents a file in a file system. +/// +public class FileProxy : IFileProxy +{ + private readonly string rootPath; + private readonly string filepath; + + /// + /// The path of the underlying file relatively to the root one. + /// + public string RelativePath { get; private set; } + + /// + /// The filename of the underlying file. + /// + public string Filename { get; private set; } + + /// + /// Initializes a new instance of the FileProxy class. + /// + /// The root path of the underlying file's relative one. + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// + /// + public FileProxy(string rootPath, string relativePath, string filename) + { + if (filename == string.Empty) + throw new ArgumentException($"Value can't be empty. Parameter name: filename."); + + if (filename == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: filename.", default(Exception)); + + this.rootPath = rootPath; + this.RelativePath = relativePath; + this.Filename = filename; + this.filepath = AbsolutePath.Combine(this.rootPath, this.RelativePath, this.Filename); + } + + /// + /// Checks if the underlying file exists. + /// + /// Returns a flag indicating if the underlying file exists. + public async Task ExistsAsync() + { + return await Task.Factory.StartNew(() => File.Exists(this.filepath)); + } + + /// + /// Reads content of the underlying file as a stream. + /// + /// Content of the underlying file as a stream. + /// + /// + /// + /// + /// + public async Task ReadStreamAsync() + { + return await Task.Factory.StartNew(() => + { + try + { + return File.Open(this.filepath, FileMode.Open); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.FileNotFoundException e) + { + throw new FileNotFoundException($"File not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Reads content of the underlying file as a byte array. + /// + /// Content of the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task ReadBytesAsync() + { + return await Task.Factory.StartNew(() => + { + try + { + return File.ReadAllBytes(this.filepath); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (SecurityException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.FileNotFoundException e) + { + throw new FileNotFoundException($"File not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Reads content of the underlying file as a text string. + /// + /// Content of the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task ReadTextAsync() + { + return await Task.Factory.StartNew(() => + { + try + { + return File.ReadAllText(this.filepath); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (SecurityException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.FileNotFoundException e) + { + throw new FileNotFoundException($"File not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Writes content to the underlying file as a stream. + /// + /// Content to write to the underlying file as a stream. + /// + /// + /// + /// + /// + public async Task WriteStreamAsync(Stream inputStream) + { + if (inputStream == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: inputStream.", default(Exception)); + + await Task.Factory.StartNew(() => + { + try + { + using (Stream outputStream = File.Create(this.filepath)) + inputStream.CopyTo(outputStream); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Writes content to the underlying file as a byte array. + /// + /// Content to write to the underlying file as a byte array. + /// + /// + /// + /// + /// + public async Task WriteBytesAsync(byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: bytes.", default(Exception)); + + await Task.Factory.StartNew(() => + { + try + { + File.WriteAllBytes(this.filepath, bytes); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (SecurityException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Writes content to the underlying file as a text string. + /// + /// Content to write to the underlying file as a text string. + /// + /// + /// + /// + /// + public async Task WriteTextAsync(string text) + { + if (text == null) + throw new ArgumentNullException($"Value can't be null. Parameter name: text.", default(Exception)); + + await Task.Factory.StartNew(() => + { + try + { + File.WriteAllText(this.filepath, text); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (SecurityException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } + + /// + /// Deletes the underlying file. + /// + /// + /// + /// + /// + /// + public async Task DeleteAsync() + { + await Task.Factory.StartNew(() => + { + if (!File.Exists(this.filepath)) + throw new FileNotFoundException($"File not found: \"{this.filepath}\".", null); + + try + { + File.Delete(this.filepath); + } + + catch (UnauthorizedAccessException e) + { + throw new AccessDeniedException($"Access denied: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.DirectoryNotFoundException e) + { + throw new DirectoryNotFoundException($"Directory not found: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (System.IO.PathTooLongException e) + { + throw new PathTooLongException($"Path too long: \"{this.filepath}\". See inner exception for details.", e); + } + + catch (Exception e) + { + throw new FileStorageException($"Generic file storage exception: \"{this.filepath}\". See inner exception for details.", e); + } + }); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.FileSystem/FileStorage.cs b/src/ExtCore.FileStorage.FileSystem/FileStorage.cs new file mode 100644 index 0000000..2d308f6 --- /dev/null +++ b/src/ExtCore.FileStorage.FileSystem/FileStorage.cs @@ -0,0 +1,45 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.FileStorage.Abstractions; +using Microsoft.Extensions.Options; + +namespace ExtCore.FileStorage.FileSystem; + +/// +/// Implements the IFileStorage interface and represents a file storage in a file system. +/// +public class FileStorage : IFileStorage +{ + private readonly string rootPath; + + /// + /// Initializes a new instance of the FileStorage class. + /// + /// The options that are used to configure the file storage root path. + public FileStorage(IOptions options) + { + this.rootPath = options.Value.RootPath; + } + + /// + /// Creates a directory proxy which allows to manipulate an underlying directory with a specified relative path. + /// + /// The path of the underlying directory relatively to the root one. + /// Created directory proxy. + public IDirectoryProxy CreateDirectoryProxy(string relativePath) + { + return new DirectoryProxy(this.rootPath, relativePath); + } + + /// + /// Creates a file proxy which allows to manipulate an underlying file with a specified relative path and a filename. + /// + /// The path of the underlying file relatively to the root one. + /// The filename of the underlying file. + /// Created file proxy. + public IFileProxy CreateFileProxy(string relativePath, string filename) + { + return new FileProxy(this.rootPath, relativePath, filename); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage.FileSystem/icon.png b/src/ExtCore.FileStorage.FileSystem/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.FileStorage.FileSystem/icon.png differ diff --git a/src/ExtCore.FileStorage/Actions/AddFileStorageAction.cs b/src/ExtCore.FileStorage/Actions/AddFileStorageAction.cs new file mode 100644 index 0000000..2debc52 --- /dev/null +++ b/src/ExtCore.FileStorage/Actions/AddFileStorageAction.cs @@ -0,0 +1,49 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using ExtCore.FileStorage.Abstractions; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.FileStorage.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// registers found implementation of the IFileStorage interface inside the DI. +/// +public class AddFileStorageAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Registers found implementation of the IFileStorage interface inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + Type type = ExtensionManager.GetImplementations()?.FirstOrDefault(t => !t.GetTypeInfo().IsAbstract); + + if (type == null) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.FileStorage"); + + logger.LogError("Implementation of ExtCore.FileStorage.Abstractions.IFileStorage not found"); + return; + } + + services.AddScoped(typeof(IFileStorage), type); + } +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage/ExtCore.FileStorage.csproj b/src/ExtCore.FileStorage/ExtCore.FileStorage.csproj new file mode 100644 index 0000000..69afbe7 --- /dev/null +++ b/src/ExtCore.FileStorage/ExtCore.FileStorage.csproj @@ -0,0 +1,16 @@ + + + + + The ExtCore.FileStorage extension component. Based on the ExtCore framework. + net8.0 + ExtCore.FileStorage + ExtCore.FileStorage + + + + + + + + diff --git a/src/ExtCore.FileStorage/Extension.cs b/src/ExtCore.FileStorage/Extension.cs new file mode 100644 index 0000000..291ed1d --- /dev/null +++ b/src/ExtCore.FileStorage/Extension.cs @@ -0,0 +1,32 @@ +// Copyright © 2018 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.FileStorage; + +/// +/// Overrides the ExtensionBase class and provides the ExtCore.FileStorage extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.FileStorage"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.FileStorage/icon.png b/src/ExtCore.FileStorage/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.FileStorage/icon.png differ diff --git a/src/ExtCore.Infrastructure/Actions/IConfigureAction.cs b/src/ExtCore.Infrastructure/Actions/IConfigureAction.cs new file mode 100644 index 0000000..7820511 --- /dev/null +++ b/src/ExtCore.Infrastructure/Actions/IConfigureAction.cs @@ -0,0 +1,30 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Builder; + +namespace ExtCore.Infrastructure.Actions; + +/// +/// Describes an action that must be executed inside the Configure method of a web application's Startup class +/// and might be used by the extensions to configure a web application's request pipeline. +/// +public interface IConfigureAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + int Priority { get; } + + /// + /// Contains any code that must be executed inside the Configure method of the web application's Startup class. + /// + /// + /// Will be provided by the ExtCore and might be used to configure a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + void Execute(IApplicationBuilder applicationBuilder, IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/src/ExtCore.Infrastructure/Actions/IConfigureServicesAction.cs b/src/ExtCore.Infrastructure/Actions/IConfigureServicesAction.cs new file mode 100644 index 0000000..e202d9c --- /dev/null +++ b/src/ExtCore.Infrastructure/Actions/IConfigureServicesAction.cs @@ -0,0 +1,30 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace ExtCore.Infrastructure.Actions; + +/// +/// Describes an action that must be executed inside the ConfigureServices method of a web application's Startup class +/// and might be used by the extensions to register any service inside the DI. +/// +public interface IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + int Priority { get; } + + /// + /// Contains any code that must be executed inside the ConfigureServices method of the web application's Startup class. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + void Execute(IServiceCollection services, IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/src/ExtCore.Infrastructure/ExtCore.Infrastructure.csproj b/src/ExtCore.Infrastructure/ExtCore.Infrastructure.csproj index e6cb898..3fe4246 100644 --- a/src/ExtCore.Infrastructure/ExtCore.Infrastructure.csproj +++ b/src/ExtCore.Infrastructure/ExtCore.Infrastructure.csproj @@ -1,23 +1,15 @@  + - Free, open source and cross-platform framework for creating modular and extendable web applications based on ASP.NET Core 1.1.0. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + Free, open source and cross-platform framework for creating modular and extendable web applications based on ASP.NET Core. + net8.0 ExtCore.Infrastructure ExtCore.Infrastructure - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - 1.6.1 - - - - + diff --git a/src/ExtCore.Infrastructure/ExtensionBase.cs b/src/ExtCore.Infrastructure/ExtensionBase.cs index 8a7c1cd..d33dd08 100644 --- a/src/ExtCore.Infrastructure/ExtensionBase.cs +++ b/src/ExtCore.Infrastructure/ExtensionBase.cs @@ -1,84 +1,35 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +namespace ExtCore.Infrastructure; -namespace ExtCore.Infrastructure +/// +/// Implements the IExtension interface and represents default extension behavior. +/// +public abstract class ExtensionBase : IExtension { /// - /// Implements the IExtension interface and represents default extension behavior - /// that might be overridden by the derived classes. + /// Gets the name of the extension. /// - public abstract class ExtensionBase : IExtension - { - protected IServiceProvider serviceProvider; - protected IConfigurationRoot configurationRoot; - protected ILogger logger; + public virtual string Name => this.GetType().FullName; - /// - /// Gets the name of the extension. - /// - public virtual string Name - { - get - { - return this.GetType().Name; - } - } - - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the ConfigureServices method of a web application Startup class. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - public virtual IEnumerable>> ConfigureServicesActionsByPriorities - { - get - { - return null; - } - } + /// + /// Gets the description of the extension. + /// + public virtual string Description => null; - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the Configure method of a web application Startup class. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - public virtual IEnumerable>> ConfigureActionsByPriorities - { - get - { - return null; - } - } + /// + /// Gets the URL of the extension. + /// + public virtual string Url => null; - /// - /// Sets the service provider that contains services currently registered inside the DI. - /// Only these services will be available for the extension. - /// - /// The service provider to set. - public virtual void SetServiceProvider(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - this.logger = this.serviceProvider.GetService().CreateLogger(); - } + /// + /// Gets the version of the extension. + /// + public virtual string Version => null; - /// - /// Sets the configuration root that is built in a web application Startup class constructor. - /// This configuration root is only used to configure the extensions and will not be used to - /// provide configuration properties to the controllers etc. - /// - /// The configuration root to set. - public virtual void SetConfigurationRoot(IConfigurationRoot configurationRoot) - { - this.configurationRoot = configurationRoot; - } - } + /// + /// Gets the authors of the extension (separated by commas). + /// + public virtual string Authors => null; } \ No newline at end of file diff --git a/src/ExtCore.Infrastructure/ExtensionManager.cs b/src/ExtCore.Infrastructure/ExtensionManager.cs index 67cfdef..aef30e3 100644 --- a/src/ExtCore.Infrastructure/ExtensionManager.cs +++ b/src/ExtCore.Infrastructure/ExtensionManager.cs @@ -2,226 +2,266 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace ExtCore.Infrastructure +namespace ExtCore.Infrastructure; + +/// +/// Represents the assembly cache with the mechanism of getting implementations or instances of a given type. +/// This is the global access point to the ExtCore type discovering mechanism. +/// +public static class ExtensionManager { + private static IEnumerable assemblies; + private static ConcurrentDictionary> types; + /// - /// Represents assembly cache with the mechanism of getting implementations or instances of a given type. - /// Also it allows to get all the instances of the IExtension interface - /// with the additional cache. This is the global access point to the types discovered by the ExtCore. + /// Gets the cached assemblies that have been set by the SetAssemblies method. /// - public static class ExtensionManager + public static IEnumerable Assemblies { - private static IEnumerable assemblies; - private static IEnumerable extensions; - - /// - /// Gets the cached assemblies that has been set by the SetAssemblies method. - /// - public static IEnumerable Assemblies + get { - get - { - return ExtensionManager.assemblies; - } + return ExtensionManager.assemblies; } + } - /// - /// Gets the cached instances of the IExtension interface. - /// - public static IEnumerable Extensions - { - get - { - if (ExtensionManager.extensions == null) - ExtensionManager.extensions = ExtensionManager.GetInstances(); + /// + /// Sets the assemblies and invalidates the type cache. + /// + /// The assemblies to set. + public static void SetAssemblies(IEnumerable assemblies) + { + ExtensionManager.assemblies = assemblies; + ExtensionManager.types = new ConcurrentDictionary>(); + } - return ExtensionManager.extensions; - } - } + /// + /// Gets the first implementation of the type specified by the type parameter, or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the same type(s) is requested. + /// + /// The first found implementation of the given type. + public static Type GetImplementation(bool useCaching = false) + { + return ExtensionManager.GetImplementation(null, useCaching); + } - /// - /// Sets the discovered assemblies and invalidates the IExtension interface - /// instances cache. - /// - /// The assemblies to set. - public static void SetAssemblies(IEnumerable assemblies) - { - ExtensionManager.assemblies = assemblies; - ExtensionManager.extensions = null; - } + /// + /// Gets the first implementation of the type specified by the type parameter and located in the assemblies + /// filtered by the predicate, or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the same type(s) is requested. + /// + /// The first found implementation of the given type. + public static Type GetImplementation(Func predicate, bool useCaching = false) + { + return ExtensionManager.GetImplementations(predicate, useCaching).FirstOrDefault(); + } - /// - /// Gets the first implementation of the type specified by the type parameter or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// - public static Type GetImplementation() - { - return ExtensionManager.GetImplementation(null); - } + /// + /// Gets the implementations of the type specified by the type parameter. + /// + /// The type parameter to find implementations of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the same type(s) is requested. + /// + /// Found implementations of the given type. + public static IEnumerable GetImplementations(bool useCaching = false) + { + return ExtensionManager.GetImplementations(null, useCaching); + } - /// - /// Gets the first implementation of the type specified by the type parameter and located in the assemblies - /// filtered by the predicate or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// The predicate to filter the assemblies. - /// - public static Type GetImplementation(Func predicate) - { - return ExtensionManager.GetImplementations(predicate).FirstOrDefault(); - } + /// + /// Gets the implementations of the type specified by the type parameter and located in the assemblies + /// filtered by the predicate. + /// + /// The type parameter to find implementations of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the same type(s) is requested. + /// + /// Found implementations of the given type. + public static IEnumerable GetImplementations(Func predicate, bool useCaching = false) + { + Type type = typeof(T); - /// - /// Gets the implementations of the type specified by the type parameter. - /// - /// The type parameter to find implementations of. - /// - public static IEnumerable GetImplementations() - { - return ExtensionManager.GetImplementations(null); - } + if (useCaching && ExtensionManager.types.ContainsKey(type)) + return ExtensionManager.types[type]; - /// - /// Gets the implementations of the type specified by the type parameter and located in the assemblies - /// filtered by the predicate. - /// - /// The type parameter to find implementations of. - /// The predicate to filter the assemblies. - /// - public static IEnumerable GetImplementations(Func predicate) - { - List implementations = new List(); + List implementations = new List(); - foreach (Assembly assembly in ExtensionManager.GetAssemblies(predicate)) - foreach (Type type in assembly.GetTypes()) - if (typeof(T).GetTypeInfo().IsAssignableFrom(type) && type.GetTypeInfo().IsClass) - implementations.Add(type); + foreach (Assembly assembly in ExtensionManager.GetAssemblies(predicate)) + foreach (Type exportedType in assembly.GetExportedTypes()) + if (type.GetTypeInfo().IsAssignableFrom(exportedType) && exportedType.GetTypeInfo().IsClass) + implementations.Add(exportedType); - return implementations; - } + if (useCaching) + ExtensionManager.types[type] = implementations; - /// - /// Gets the new instance of the first implementation of the type specified by the type parameter - /// or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// - public static T GetInstance() - { - return ExtensionManager.GetInstance(null, new object[] { }); - } + return implementations; + } - /// - /// Gets the new instance (using constructor that matches the arguments) of the first implementation - /// of the type specified by the type parameter or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// The arguments to be passed to the constructor. - /// - public static T GetInstance(params object[] args) - { - return ExtensionManager.GetInstance(null, args); - } + /// + /// Gets the new instance of the first implementation of the type specified by the type parameter, + /// or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The instance of the first found implementation of the given type. + public static T GetInstance(bool useCaching = false) + { + return ExtensionManager.GetInstance(null, useCaching, new object[] { }); + } - /// - /// Gets the new instance of the first implementation of the type specified by the type parameter - /// and located in the assemblies filtered by the predicate or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// The predicate to filter the assemblies. - /// - public static T GetInstance(Func predicate) - { - return ExtensionManager.GetInstances(predicate).FirstOrDefault(); - } + /// + /// Gets the new instance (using constructor that matches the arguments) of the first implementation + /// of the type specified by the type parameter or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The arguments to be passed to the constructor. + /// The instance of the first found implementation of the given type. + public static T GetInstance(bool useCaching = false, params object[] args) + { + return ExtensionManager.GetInstance(null, useCaching, args); + } - /// - /// Gets the new instance (using constructor that matches the arguments) of the first implementation - /// of the type specified by the type parameter and located in the assemblies filtered by the predicate - /// or null if no implementations found. - /// - /// The type parameter to find implementation of. - /// The predicate to filter the assemblies. - /// The arguments to be passed to the constructor. - /// - public static T GetInstance(Func predicate, params object[] args) - { - return ExtensionManager.GetInstances(predicate, args).FirstOrDefault(); - } + /// + /// Gets the new instance of the first implementation of the type specified by the type parameter + /// and located in the assemblies filtered by the predicate or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The instance of the first found implementation of the given type. + public static T GetInstance(Func predicate, bool useCaching = false) + { + return ExtensionManager.GetInstances(predicate, useCaching).FirstOrDefault(); + } - /// - /// Gets the new instances of the implementations of the type specified by the type parameter - /// or empty enumeration if no implementations found. - /// - /// The type parameter to find implementations of. - /// - public static IEnumerable GetInstances() - { - return ExtensionManager.GetInstances(null, new object[] { }); - } + /// + /// Gets the new instance (using constructor that matches the arguments) of the first implementation + /// of the type specified by the type parameter and located in the assemblies filtered by the predicate + /// or null if no implementations found. + /// + /// The type parameter to find implementation of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The arguments to be passed to the constructor. + /// The instance of the first found implementation of the given type. + public static T GetInstance(Func predicate, bool useCaching = false, params object[] args) + { + return ExtensionManager.GetInstances(predicate, useCaching, args).FirstOrDefault(); + } - /// - /// Gets the new instances (using constructor that matches the arguments) of the implementations - /// of the type specified by the type parameter or empty enumeration if no implementations found. - /// - /// The type parameter to find implementations of. - /// The arguments to be passed to the constructors. - /// - public static IEnumerable GetInstances(params object[] args) - { - return ExtensionManager.GetInstances(null, args); - } + /// + /// Gets the new instances of the implementations of the type specified by the type parameter + /// or empty enumeration if no implementations found. + /// + /// The type parameter to find implementations of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The instances of the found implementations of the given type. + public static IEnumerable GetInstances(bool useCaching = false) + { + return ExtensionManager.GetInstances(null, useCaching, new object[] { }); + } - /// - /// Gets the new instances of the implementations of the type specified by the type parameter - /// and located in the assemblies filtered by the predicate or empty enumeration - /// if no implementations found. - /// - /// The type parameter to find implementations of. - /// The predicate to filter the assemblies. - /// - public static IEnumerable GetInstances(Func predicate) - { - return ExtensionManager.GetInstances(predicate, new object[] { }); - } + /// + /// Gets the new instances (using constructor that matches the arguments) of the implementations + /// of the type specified by the type parameter or empty enumeration if no implementations found. + /// + /// The type parameter to find implementations of. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The arguments to be passed to the constructors. + /// The instances of the found implementations of the given type. + public static IEnumerable GetInstances(bool useCaching = false, params object[] args) + { + return ExtensionManager.GetInstances(null, useCaching, args); + } - /// - /// Gets the new instances (using constructor that matches the arguments) of the implementations - /// of the type specified by the type parameter and located in the assemblies filtered by the predicate - /// or empty enumeration if no implementations found. - /// - /// The type parameter to find implementations of. - /// The predicate to filter the assemblies. - /// The arguments to be passed to the constructors. - /// - public static IEnumerable GetInstances(Func predicate, params object[] args) - { - List instances = new List(); + /// + /// Gets the new instances of the implementations of the type specified by the type parameter + /// and located in the assemblies filtered by the predicate or empty enumeration + /// if no implementations found. + /// + /// The type parameter to find implementations of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The instances of the found implementations of the given type. + public static IEnumerable GetInstances(Func predicate, bool useCaching = false) + { + return ExtensionManager.GetInstances(predicate, useCaching, new object[] { }); + } - foreach (Type implementation in ExtensionManager.GetImplementations()) + /// + /// Gets the new instances (using constructor that matches the arguments) of the implementations + /// of the type specified by the type parameter and located in the assemblies filtered by the predicate + /// or empty enumeration if no implementations found. + /// + /// The type parameter to find implementations of. + /// The predicate to filter the assemblies. + /// + /// Determines whether the type cache should be used to avoid assemblies scanning next time, + /// when the instance(s) of the same type(s) is requested. + /// + /// The arguments to be passed to the constructors. + /// The instances of the found implementations of the given type. + public static IEnumerable GetInstances(Func predicate, bool useCaching = false, params object[] args) + { + List instances = new List(); + + foreach (Type implementation in ExtensionManager.GetImplementations(predicate, useCaching)) + { + if (!implementation.GetTypeInfo().IsAbstract) { - if (!implementation.GetTypeInfo().IsAbstract) - { - T instance = (T)Activator.CreateInstance(implementation, args); + T instance = (T)Activator.CreateInstance(implementation, args); - instances.Add(instance); - } + instances.Add(instance); } - - return instances; } - private static IEnumerable GetAssemblies(Func predicate) - { - if (predicate == null) - return ExtensionManager.Assemblies; + return instances; + } - return ExtensionManager.Assemblies.Where(predicate); - } + private static IEnumerable GetAssemblies(Func predicate) + { + if (predicate == null) + return ExtensionManager.Assemblies; + + return ExtensionManager.Assemblies.Where(predicate); } } \ No newline at end of file diff --git a/src/ExtCore.Infrastructure/IExtension.cs b/src/ExtCore.Infrastructure/IExtension.cs index ca24c2a..a4b0693 100644 --- a/src/ExtCore.Infrastructure/IExtension.cs +++ b/src/ExtCore.Infrastructure/IExtension.cs @@ -1,55 +1,35 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +namespace ExtCore.Infrastructure; -namespace ExtCore.Infrastructure +/// +/// Describes an extension. +/// +public interface IExtension { /// - /// Describes an extension with the mechanism of executing prioritized (defined in a specific order) - /// actions (code fragments) within the ConfigureServices and Configure methods of a web application - /// Startup class. + /// Gets the name of the extension. /// - public interface IExtension - { - /// - /// Gets the name of the extension. - /// - string Name { get; } + string Name { get; } - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the ConfigureServices method of a web application Startup class. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - IEnumerable>> ConfigureServicesActionsByPriorities { get; } + /// + /// Gets the description of the extension. + /// + string Description { get; } - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the Configure method of a web application Startup class. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - IEnumerable>> ConfigureActionsByPriorities { get; } + /// + /// Gets the URL of the extension. + /// + string Url { get; } - /// - /// Sets the service provider that contains services currently registered inside the DI. - /// Only these services will be available for the extension. - /// - /// The service provider to set. - void SetServiceProvider(IServiceProvider serviceProvider); + /// + /// Gets the version of the extension. + /// + string Version { get; } - /// - /// Sets the configuration root that is built in a web application Startup class constructor. - /// This configuration root is only used to configure the extensions and will not be used to - /// provide configuration properties to the controllers etc. - /// - /// The configuration root to set. - void SetConfigurationRoot(IConfigurationRoot configurationRoot); - } + /// + /// Gets the authors of the extension (separated by commas). + /// + string Authors { get; } } \ No newline at end of file diff --git a/src/ExtCore.Infrastructure/icon.png b/src/ExtCore.Infrastructure/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Infrastructure/icon.png differ diff --git a/src/ExtCore.Mvc.Infrastructure/Actions/IAddMvcAction.cs b/src/ExtCore.Mvc.Infrastructure/Actions/IAddMvcAction.cs new file mode 100644 index 0000000..a84006e --- /dev/null +++ b/src/ExtCore.Mvc.Infrastructure/Actions/IAddMvcAction.cs @@ -0,0 +1,30 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace ExtCore.Mvc.Infrastructure.Actions; + +/// +/// Describes an action that must be executed inside the AddMvc method and might be used by the extensions +/// to configure the MVC. +/// +public interface IAddMvcAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + int Priority { get; } + + /// + /// Contains any code that must be executed inside the AddMvc method. + /// + /// + /// Will be provided by the ExtCore.Mvc and might be used to configure the MVC. + /// + /// + /// Will be provided by the ExtCore.Mvc and might be used to get any service that is registered inside the DI at this moment. + /// + void Execute(IMvcBuilder mvcBuilder, IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/src/ExtCore.Mvc.Infrastructure/Actions/IUseEndpointsAction.cs b/src/ExtCore.Mvc.Infrastructure/Actions/IUseEndpointsAction.cs new file mode 100644 index 0000000..9c815b9 --- /dev/null +++ b/src/ExtCore.Mvc.Infrastructure/Actions/IUseEndpointsAction.cs @@ -0,0 +1,30 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Routing; + +namespace ExtCore.Mvc.Infrastructure.Actions; + +/// +/// Describes an action that must be executed inside the UseEndpoints method and might be used by the extensions +/// to configure the endpoints. +/// +public interface IUseEndpointsAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + int Priority { get; } + + /// + /// Contains any code that must be executed inside the UseEndpoints method. + /// + /// + /// Will be provided by the ExtCore.Mvc and might be used to configure the endpoints. + /// + /// + /// Will be provided by the ExtCore.Mvc and might be used to get any service that is registered inside the DI at this moment. + /// + void Execute(IEndpointRouteBuilder endpointRouteBuilder, IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/src/ExtCore.Mvc.Infrastructure/ExtCore.Mvc.Infrastructure.csproj b/src/ExtCore.Mvc.Infrastructure/ExtCore.Mvc.Infrastructure.csproj index 6d5a4d5..37932e2 100644 --- a/src/ExtCore.Mvc.Infrastructure/ExtCore.Mvc.Infrastructure.csproj +++ b/src/ExtCore.Mvc.Infrastructure/ExtCore.Mvc.Infrastructure.csproj @@ -1,23 +1,15 @@  + - Part of the ExtCore.Mvc ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Mvc extension component. Based on the ExtCore framework. + net8.0 ExtCore.Mvc.Infrastructure ExtCore.Mvc.Infrastructure - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - - - - diff --git a/src/ExtCore.Mvc.Infrastructure/ExtensionBase.cs b/src/ExtCore.Mvc.Infrastructure/ExtensionBase.cs deleted file mode 100644 index 9d4a871..0000000 --- a/src/ExtCore.Mvc.Infrastructure/ExtensionBase.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; - -namespace ExtCore.Mvc.Infrastructure -{ - /// - /// Implements the IExtension interface and represents default MVC extension behavior - /// that might be overridden by the derived classes. - /// - public abstract class ExtensionBase : ExtCore.Infrastructure.ExtensionBase, IExtension - { - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the AddMvc extension method while it is executed by the ExtCore.Mvc extension to configure the MVC. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - public virtual IEnumerable>> AddMvcActionsByPriorities - { - get - { - return null; - } - } - - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the UseMvc extension method while it is executed by the ExtCore.Mvc extension to configure the MVC. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - public virtual IEnumerable>> UseMvcActionsByPriorities - { - get - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Mvc.Infrastructure/IExtension.cs b/src/ExtCore.Mvc.Infrastructure/IExtension.cs deleted file mode 100644 index 6b596e1..0000000 --- a/src/ExtCore.Mvc.Infrastructure/IExtension.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; - -namespace ExtCore.Mvc.Infrastructure -{ - /// - /// Describes an MVC extension with the mechanism of executing prioritized (defined in a specific order) - /// actions (code fragments) within the AddMvc and UseMvc extension methods while they are executed - /// by the ExtCore.Mvc extension to configure the MVC. - /// - public interface IExtension : ExtCore.Infrastructure.IExtension - { - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the AddMvc extension method while it is executed by the ExtCore.Mvc extension to configure the MVC. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - IEnumerable>> AddMvcActionsByPriorities { get; } - - /// - /// Gets the prioritized (defined in a specific order) actions (code fragments) that must be executed - /// within the UseMvc extension method while it is executed by the ExtCore.Mvc extension to configure the MVC. - /// Priority is set by the key, while the action is set by the value of the KeyValuePair. Before these actions are - /// executed they will be merged with the actions of all other extensions according to the priorities. - /// - IEnumerable>> UseMvcActionsByPriorities { get; } - } -} \ No newline at end of file diff --git a/src/ExtCore.Mvc.Infrastructure/icon.png b/src/ExtCore.Mvc.Infrastructure/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Mvc.Infrastructure/icon.png differ diff --git a/src/ExtCore.Mvc/Actions/AddMvcAction.cs b/src/ExtCore.Mvc/Actions/AddMvcAction.cs new file mode 100644 index 0000000..7d29d22 --- /dev/null +++ b/src/ExtCore.Mvc/Actions/AddMvcAction.cs @@ -0,0 +1,50 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using ExtCore.Mvc.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.Mvc.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// registers the MVC services inside the DI. +/// +public class AddMvcAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 10000; + + /// + /// Registers the MVC services inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + IMvcBuilder mvcBuilder = services.AddMvc(); + + foreach (Assembly assembly in ExtensionManager.Assemblies) + mvcBuilder.AddApplicationPart(assembly); + + foreach (IAddMvcAction action in ExtensionManager.GetInstances().OrderBy(a => a.Priority)) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.Mvc"); + + logger.LogInformation("Executing AddMvc action '{0}'", action.GetType().FullName); + action.Execute(mvcBuilder, serviceProvider); + } + } +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/Actions/AddStaticFilesAction.cs b/src/ExtCore.Mvc/Actions/AddStaticFilesAction.cs new file mode 100644 index 0000000..d058e7d --- /dev/null +++ b/src/ExtCore.Mvc/Actions/AddStaticFilesAction.cs @@ -0,0 +1,51 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; + +namespace ExtCore.Mvc.Actions; + +/// +/// Implements the IConfigureServicesAction interface and +/// creates and registers the composite file provider that contains resources from all the extensions. +/// +public class AddStaticFilesAction : IConfigureServicesAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Creates and registers the composite file provider that contains resources from all the extensions. + /// + /// + /// Will be provided by the ExtCore and might be used to register any service inside the DI. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IServiceCollection services, IServiceProvider serviceProvider) + { + serviceProvider.GetService().WebRootFileProvider = this.CreateCompositeFileProvider(serviceProvider); + } + + private IFileProvider CreateCompositeFileProvider(IServiceProvider serviceProvider) + { + IFileProvider[] fileProviders = new IFileProvider[] { + serviceProvider.GetService().WebRootFileProvider + }; + + return new CompositeFileProvider( + fileProviders.Concat( + ExtensionManager.Assemblies.Select(a => new EmbeddedFileProvider(a, a.GetName().Name)) + ) + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/Actions/UseEndpointsAction.cs b/src/ExtCore.Mvc/Actions/UseEndpointsAction.cs new file mode 100644 index 0000000..3eb5c8b --- /dev/null +++ b/src/ExtCore.Mvc/Actions/UseEndpointsAction.cs @@ -0,0 +1,50 @@ +// Copyright © 2019 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using ExtCore.Mvc.Infrastructure.Actions; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.Mvc.Actions; + +/// +/// Implements the IConfigureAction interface and registers the +/// endpoints middleware inside a web application's request pipeline. +/// +public class UseEndpointsAction : IConfigureAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 11000; + + /// + /// Registers the endpoints middleware inside a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to configure a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider serviceProvider) + { + applicationBuilder.UseEndpoints( + endpointRouteBuilder => + { + foreach (IUseEndpointsAction action in ExtensionManager.GetInstances().OrderBy(a => a.Priority)) + { + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.Mvc"); + + logger.LogInformation("Executing UseEndpoints action '{0}'", action.GetType().FullName); + action.Execute(endpointRouteBuilder, serviceProvider); + } + } + ); + } +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/Actions/UseRoutingAction.cs b/src/ExtCore.Mvc/Actions/UseRoutingAction.cs new file mode 100644 index 0000000..89b3318 --- /dev/null +++ b/src/ExtCore.Mvc/Actions/UseRoutingAction.cs @@ -0,0 +1,34 @@ +// Copyright © 2019 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using ExtCore.Infrastructure.Actions; +using Microsoft.AspNetCore.Builder; + +namespace ExtCore.Mvc.Actions; + +/// +/// Implements the IConfigureAction interface and registers the +/// routing middleware inside a web application's request pipeline. +/// +public class UseRoutingAction : IConfigureAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 10000; + + /// + /// Registers the routing middleware inside a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to configure a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider serviceProvider) + { + applicationBuilder.UseRouting(); + } +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/Actions/UseStaticFilesAction.cs b/src/ExtCore.Mvc/Actions/UseStaticFilesAction.cs new file mode 100644 index 0000000..d675ffa --- /dev/null +++ b/src/ExtCore.Mvc/Actions/UseStaticFilesAction.cs @@ -0,0 +1,39 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using ExtCore.Infrastructure.Actions; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace ExtCore.Mvc.Actions; + +/// +/// Implements the IConfigureAction interface and registers the +/// static files middleware inside a web application's request pipeline. +/// +public class UseStaticFilesAction : IConfigureAction +{ + /// + /// Priority of the action. The actions will be executed in the order specified by the priority (from smallest to largest). + /// + public int Priority => 1000; + + /// + /// Registers the static files middleware inside a web application's request pipeline. + /// + /// + /// + /// Will be provided by the ExtCore and might be used to configure a web application's request pipeline. + /// + /// + /// Will be provided by the ExtCore and might be used to get any service that is registered inside the DI at this moment. + /// + public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider serviceProvider) + { + IOptions options = serviceProvider.GetService>(); + + applicationBuilder.UseStaticFiles(options?.Value); + } +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/CompositeFileProvider.cs b/src/ExtCore.Mvc/CompositeFileProvider.cs deleted file mode 100644 index c6cc605..0000000 --- a/src/ExtCore.Mvc/CompositeFileProvider.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Primitives; - -namespace ExtCore.Mvc -{ - /// - /// Implements the IFileProvider interface and represents composite file provider - /// that is built from the set of other file providers like PhysicalFileProvider, - /// EmbeddedFileProvider etc. It is used to make it possible to resolve - /// files that are located in a file system, assemblies etc. - /// - public class CompositeFileProvider : IFileProvider - { - private readonly IEnumerable fileProviders; - - /// - /// Initializes a new instance of the CompositeFileProvider class. - /// - /// The set of the file providers to look for the files in. - public CompositeFileProvider(IEnumerable fileProviders) - { - this.fileProviders = fileProviders; - } - - public IDirectoryContents GetDirectoryContents(string subpath) - { - foreach (IFileProvider fileProvider in this.fileProviders) - { - IDirectoryContents directoryContents = fileProvider.GetDirectoryContents(subpath); - - if (directoryContents != null && directoryContents.Exists) - return directoryContents; - } - - return new NonexistentDirectoryContents(); - } - - public IFileInfo GetFileInfo(string subpath) - { - foreach (IFileProvider fileProvider in this.fileProviders) - { - IFileInfo fileInfo = fileProvider.GetFileInfo(subpath); - - if (fileInfo != null && fileInfo.Exists) - return fileInfo; - } - - return new NonexistentFileInfo(subpath); - } - - public IChangeToken Watch(string filter) - { - foreach (IFileProvider fileProvider in this.fileProviders) - { - IChangeToken changeToken = fileProvider.Watch(filter); - - if (changeToken != null) - return changeToken; - } - - return NonexistentChangeToken.Singleton; - } - } - - internal class NonexistentDirectoryContents : IDirectoryContents - { - public bool Exists - { - get - { - return false; - } - } - - public IEnumerator GetEnumerator() - { - return Enumerable.Empty().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } - - internal class NonexistentFileInfo : IFileInfo - { - private readonly string name; - - public NonexistentFileInfo(string name) - { - this.name = name; - } - - public bool Exists - { - get - { - return false; - } - } - - public bool IsDirectory - { - get - { - return false; - } - } - - public DateTimeOffset LastModified - { - get - { - return DateTimeOffset.MinValue; - } - } - - public long Length - { - get - { - return -1; - } - } - - public string Name - { - get - { - return this.name; - } - } - - public string PhysicalPath - { - get - { - return null; - } - } - - public Stream CreateReadStream() - { - throw new FileNotFoundException(this.name); - } - } - - internal class NonexistentChangeToken : IChangeToken - { - public static NonexistentChangeToken Singleton { get; } = new NonexistentChangeToken(); - - public bool ActiveChangeCallbacks - { - get - { - throw new NotImplementedException(); - } - } - - public bool HasChanged - { - get - { - throw new NotImplementedException(); - } - } - - public IDisposable RegisterChangeCallback(Action callback, object state) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Mvc/ExtCore.Mvc.csproj b/src/ExtCore.Mvc/ExtCore.Mvc.csproj index 84a1091..3a59bef 100644 --- a/src/ExtCore.Mvc/ExtCore.Mvc.csproj +++ b/src/ExtCore.Mvc/ExtCore.Mvc.csproj @@ -1,26 +1,19 @@  + - Part of the ExtCore.Mvc ExtCore framework extension. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + The ExtCore.Mvc extension component. Based on the ExtCore framework. + net8.0 ExtCore.Mvc ExtCore.Mvc - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - + - - - - + diff --git a/src/ExtCore.Mvc/Extension.cs b/src/ExtCore.Mvc/Extension.cs new file mode 100644 index 0000000..5feb9f0 --- /dev/null +++ b/src/ExtCore.Mvc/Extension.cs @@ -0,0 +1,32 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using ExtCore.Infrastructure; + +namespace ExtCore.Mvc; + +/// +/// Overrides the ExtensionBase class and provides the ExtCore.Mvc extension information. +/// +public class Extension : ExtensionBase +{ + /// + /// Gets the name of the extension. + /// + public override string Name => "ExtCore.Mvc"; + + /// + /// Gets the URL of the extension. + /// + public override string Url => "https://extcore.net/"; + + /// + /// Gets the version of the extension. + /// + public override string Version => "8.1.0"; + + /// + /// Gets the authors of the extension (separated by commas). + /// + public override string Authors => "Dmitry Sikorsky"; +} \ No newline at end of file diff --git a/src/ExtCore.Mvc/MvcExtension.cs b/src/ExtCore.Mvc/MvcExtension.cs deleted file mode 100644 index 64ddfba..0000000 --- a/src/ExtCore.Mvc/MvcExtension.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using ExtCore.Infrastructure; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging; - -namespace ExtCore.Mvc -{ - /// - /// Overrides the ExtensionBase class and defines the ConfigureServices and - /// Configure methods prioritized actions for configuring MVC. - /// - public class MvcExtension : ExtCore.Mvc.Infrastructure.ExtensionBase - { - /// - /// Defines prioritized action with priority = 0 that creates composite file provider (to support static fieles - /// located inside the extension assemblies) and another one prioritized action with priority = 10000 that registers - /// MVC services inside the DI. - /// - public override IEnumerable>> ConfigureServicesActionsByPriorities - { - get - { - return new Dictionary>() - { - [0] = this.AddStaticFiles, - [10000] = this.AddMvc - }; - } - } - - /// - /// Defines prioritized action with priority = 0 that adds static files support middleware and another one - /// prioritized action with priority = 10000 that adds MVC support middleware. - /// - public override IEnumerable>> ConfigureActionsByPriorities - { - get - { - return new Dictionary>() - { - [0] = this.UseStaticFiles, - [10000] = this.UseMvc - }; - } - } - - private void AddStaticFiles(IServiceCollection services) - { - this.serviceProvider.GetService().WebRootFileProvider = this.CreateCompositeFileProvider(); - } - - private void AddMvc(IServiceCollection services) - { - IMvcBuilder mvcBuilder = services.AddMvc(); - - foreach (Assembly assembly in ExtensionManager.Assemblies) - mvcBuilder.AddApplicationPart(assembly); - - mvcBuilder.AddRazorOptions( - o => - { - foreach (Assembly assembly in ExtensionManager.Assemblies) - o.FileProviders.Add(new EmbeddedFileProvider(assembly, assembly.GetName().Name)); - } - ); - - foreach (Action prioritizedAddMvcAction in this.GetPrioritizedAddMvcActions()) - { - this.logger.LogInformation("Executing prioritized AddMvc action '{0}' of {1}", this.GetActionMethodInfo(prioritizedAddMvcAction)); - prioritizedAddMvcAction(mvcBuilder); - } - } - - private Action[] GetPrioritizedAddMvcActions() - { - List>> addMvcActionsByPriorities = new List>>(); - - foreach (IExtension extension in ExtensionManager.Extensions) - if (extension is ExtCore.Mvc.Infrastructure.IExtension) - if ((extension as ExtCore.Mvc.Infrastructure.IExtension).AddMvcActionsByPriorities != null) - addMvcActionsByPriorities.AddRange((extension as ExtCore.Mvc.Infrastructure.IExtension).AddMvcActionsByPriorities); - - return this.GetPrioritizedActions(addMvcActionsByPriorities); - } - - private void UseStaticFiles(IApplicationBuilder applicationBuilder) - { - applicationBuilder.UseStaticFiles(); - } - - private void UseMvc(IApplicationBuilder applicationBuilder) - { - applicationBuilder.UseMvc( - routeBuilder => - { - foreach (Action prioritizedUseMvcAction in this.GetPrioritizedUseMvcActions()) - { - this.logger.LogInformation("Executing prioritized UseMvc action '{0}' of {1}", this.GetActionMethodInfo(prioritizedUseMvcAction)); - prioritizedUseMvcAction(routeBuilder); - } - } - ); - } - - private Action[] GetPrioritizedUseMvcActions() - { - List>> useMvcActionsByPriorities = new List>>(); - - foreach (IExtension extension in ExtensionManager.Extensions) - if (extension is ExtCore.Mvc.Infrastructure.IExtension) - if ((extension as ExtCore.Mvc.Infrastructure.IExtension).UseMvcActionsByPriorities != null) - useMvcActionsByPriorities.AddRange((extension as ExtCore.Mvc.Infrastructure.IExtension).UseMvcActionsByPriorities); - - return this.GetPrioritizedActions(useMvcActionsByPriorities); - } - - private IFileProvider CreateCompositeFileProvider() - { - IFileProvider[] fileProviders = new IFileProvider[] { - this.serviceProvider.GetService().WebRootFileProvider - }; - - return new CompositeFileProvider( - fileProviders.Concat( - ExtensionManager.Assemblies.Select(a => new EmbeddedFileProvider(a, a.GetName().Name)) - ) - ); - } - - private Action[] GetPrioritizedActions(IEnumerable>> actionsByPriorities) - { - return actionsByPriorities - .OrderBy(actionByPriority => actionByPriority.Key) - .Select(actionByPriority => actionByPriority.Value) - .ToArray(); - } - - private string[] GetActionMethodInfo(Action action) - { - MethodInfo methodInfo = action.GetMethodInfo(); - - return new string[] { methodInfo.Name, methodInfo.DeclaringType.FullName }; - } - } -} \ No newline at end of file diff --git a/src/ExtCore.Mvc/icon.png b/src/ExtCore.Mvc/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.Mvc/icon.png differ diff --git a/src/ExtCore.WebApplication/AssemblyProvider.cs b/src/ExtCore.WebApplication/AssemblyProvider.cs deleted file mode 100644 index 7bea061..0000000 --- a/src/ExtCore.WebApplication/AssemblyProvider.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.Logging; - -namespace ExtCore.WebApplication -{ - /// - /// Implements the IAssemblyProvider interface and represents - /// default assembly provider that gets assemblies from a specific folder and web application dependencies - /// with the ability to filter the discovered assemblies with the IsCandidateAssembly and - /// IsCandidateCompilationLibrary predicates. - /// - public class AssemblyProvider : IAssemblyProvider - { - protected ILogger logger; - - /// - /// Gets or sets the predicate that is used to filter discovered assemblies from a specific folder - /// before thay have been added to the resulting assemblies set. - /// - public Func IsCandidateAssembly { get; set; } - - /// - /// Gets or sets the predicate that is used to filter discovered libraries from a web application dependencies - /// before thay have been added to the resulting assemblies set. - /// - public Func IsCandidateCompilationLibrary { get; set; } - - /// - /// Initializes a new instance of the AssemblyProvider class. - /// - /// The service provider that is used to create a logger. - public AssemblyProvider(IServiceProvider serviceProvider) - { - this.logger = serviceProvider.GetService().CreateLogger(); - this.IsCandidateAssembly = assembly => - !assembly.FullName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) && - !assembly.FullName.StartsWith("System.", StringComparison.OrdinalIgnoreCase); - - this.IsCandidateCompilationLibrary = library => - !string.Equals(library.Name, "NETStandard.Library", StringComparison.OrdinalIgnoreCase) && - !library.Name.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) && - !library.Name.StartsWith("System.", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Discovers and then gets the discovered assemblies from a specific folder and web application dependencies. - /// - /// The extensions path of a web application. - /// - public IEnumerable GetAssemblies(string path) - { - List assemblies = new List(); - - assemblies.AddRange(this.GetAssembliesFromPath(path)); - assemblies.AddRange(this.GetAssembliesFromDependencyContext()); - return assemblies; - } - - private IEnumerable GetAssembliesFromPath(string path) - { - List assemblies = new List(); - - if (!string.IsNullOrEmpty(path) && Directory.Exists(path)) - { - this.logger.LogInformation("Discovering and loading assemblies from path '{0}'", path); - - foreach (string extensionPath in Directory.EnumerateFiles(path, "*.dll")) - { - Assembly assembly = null; - - try - { - assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(extensionPath); - - if (this.IsCandidateAssembly(assembly)) - { - assemblies.Add(assembly); - this.logger.LogInformation("Assembly '{0}' is discovered and loaded", assembly.FullName); - } - } - - catch (Exception e) - { - this.logger.LogWarning("Error loading assembly '{0}'", extensionPath); - this.logger.LogInformation(e.ToString()); - } - } - } - - else - { - if (string.IsNullOrEmpty(path)) - this.logger.LogWarning("Discovering and loading assemblies from path skipped: path not provided", path); - - else this.logger.LogWarning("Discovering and loading assemblies from path '{0}' skipped: path not found", path); - } - - return assemblies; - } - - private IEnumerable GetAssembliesFromDependencyContext() - { - List assemblies = new List(); - - this.logger.LogInformation("Discovering and loading assemblies from DependencyContext"); - - foreach (CompilationLibrary compilationLibrary in DependencyContext.Default.CompileLibraries) - { - if (this.IsCandidateCompilationLibrary(compilationLibrary)) - { - Assembly assembly = null; - - try - { - assembly = Assembly.Load(new AssemblyName(compilationLibrary.Name)); - assemblies.Add(assembly); - this.logger.LogInformation("Assembly '{0}' is discovered and loaded", assembly.FullName); - } - - catch (Exception e) - { - this.logger.LogWarning("Error loading assembly '{0}'", compilationLibrary.Name); - this.logger.LogInformation(e.ToString()); - } - } - } - - return assemblies; - } - } -} \ No newline at end of file diff --git a/src/ExtCore.WebApplication/DefaultAssemblyProvider.cs b/src/ExtCore.WebApplication/DefaultAssemblyProvider.cs new file mode 100644 index 0000000..df3d3f7 --- /dev/null +++ b/src/ExtCore.WebApplication/DefaultAssemblyProvider.cs @@ -0,0 +1,146 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.Logging; + +namespace ExtCore.WebApplication; + +/// +/// Implements the IAssemblyProvider interface and represents +/// default assembly provider that gets assemblies from a specific path and web application dependencies +/// with the ability to filter the discovered assemblies with the IsCandidateAssembly and +/// IsCandidateCompilationLibrary predicates. +/// +public class DefaultAssemblyProvider : IAssemblyProvider +{ + protected ILogger logger; + + /// + /// Gets or sets the predicate that is used to filter discovered assemblies from a specific folder + /// before they have been added to the resulting assemblies set. + /// + public Func IsCandidateAssembly { get; set; } + + /// + /// Gets or sets the predicate that is used to filter discovered libraries from a web application dependencies + /// before they have been added to the resulting assemblies set. + /// + public Func IsCandidateCompilationLibrary { get; set; } + + /// + /// Initializes a new instance of the AssemblyProvider class. + /// + /// The service provider that is used to create a logger. + public DefaultAssemblyProvider(IServiceProvider serviceProvider) + { + this.logger = serviceProvider.GetService().CreateLogger("ExtCore.WebApplication"); + this.IsCandidateAssembly = assembly => + !assembly.FullName.StartsWith("System", StringComparison.OrdinalIgnoreCase) && + !assembly.FullName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase); + + this.IsCandidateCompilationLibrary = library => + !library.Name.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase) && + !library.Name.StartsWith("netstandard", StringComparison.OrdinalIgnoreCase) && + !library.Name.StartsWith("System", StringComparison.OrdinalIgnoreCase) && + !library.Name.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) && + !library.Name.StartsWith("WindowsBase", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Discovers and then gets the discovered assemblies from a specific folder and web application dependencies. + /// + /// The extensions path of a web application. + /// + /// Determines whether a web application will discover and then get the discovered assemblies from the subfolders + /// of a specific folder recursively. + /// + /// The discovered and loaded assemblies. + public IEnumerable GetAssemblies(string path, bool includingSubpaths) + { + List assemblies = new List(); + + this.GetAssembliesFromDependencyContext(assemblies); + this.GetAssembliesFromPath(assemblies, path, includingSubpaths); + return assemblies; + } + + private void GetAssembliesFromDependencyContext(List assemblies) + { + this.logger.LogInformation("Discovering and loading assemblies from DependencyContext"); + + foreach (CompilationLibrary compilationLibrary in DependencyContext.Default.CompileLibraries) + { + if (this.IsCandidateCompilationLibrary(compilationLibrary)) + { + Assembly assembly = null; + + try + { + assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(compilationLibrary.Name)); + + if (!assemblies.Any(a => string.Equals(a.FullName, assembly.FullName, StringComparison.OrdinalIgnoreCase))) + { + assemblies.Add(assembly); + this.logger.LogInformation("Assembly '{0}' is discovered and loaded", assembly.FullName); + } + } + + catch (Exception e) + { + this.logger.LogWarning("Error loading assembly '{0}'", compilationLibrary.Name); + this.logger.LogWarning(e.ToString()); + } + } + } + } + + private void GetAssembliesFromPath(List assemblies, string path, bool includingSubpaths) + { + if (!string.IsNullOrEmpty(path) && Directory.Exists(path)) + { + this.logger.LogInformation("Discovering and loading assemblies from path '{0}'", path); + + foreach (string extensionPath in Directory.EnumerateFiles(path, "*.dll")) + { + Assembly assembly = null; + + try + { + assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(extensionPath); + + if (this.IsCandidateAssembly(assembly) && !assemblies.Any(a => string.Equals(a.FullName, assembly.FullName, StringComparison.OrdinalIgnoreCase))) + { + assemblies.Add(assembly); + this.logger.LogInformation("Assembly '{0}' is discovered and loaded", assembly.FullName); + } + } + + catch (Exception e) + { + this.logger.LogWarning("Error loading assembly '{0}'", extensionPath); + this.logger.LogWarning(e.ToString()); + } + } + + if (includingSubpaths) + foreach (string subpath in Directory.GetDirectories(path)) + this.GetAssembliesFromPath(assemblies, subpath, includingSubpaths); + } + + else + { + if (string.IsNullOrEmpty(path)) + this.logger.LogWarning("Discovering and loading assemblies from path skipped: path not provided", path); + + else this.logger.LogWarning("Discovering and loading assemblies from path '{0}' skipped: path not found", path); + } + } +} diff --git a/src/ExtCore.WebApplication/ExtCore.WebApplication.csproj b/src/ExtCore.WebApplication/ExtCore.WebApplication.csproj index 90dcc99..e3f1638 100644 --- a/src/ExtCore.WebApplication/ExtCore.WebApplication.csproj +++ b/src/ExtCore.WebApplication/ExtCore.WebApplication.csproj @@ -1,26 +1,20 @@  + - Free, open source and cross-platform framework for creating modular and extendable web applications based on ASP.NET Core 1.1.0. - Copyright © 2015 Dmitry Sikorsky - 1.2.0-beta1 - Dmitry Sikorsky - netstandard1.6 + Free, open source and cross-platform framework for creating modular and extendable web applications based on ASP.NET Core. + net8.0 ExtCore.WebApplication ExtCore.WebApplication - http://extcore.net/extcore_nuget_icon.png - http://extcore.net/ - + - + + - - - - + diff --git a/src/ExtCore.WebApplication/Extensions/ApplicationBuilderExtensions.cs b/src/ExtCore.WebApplication/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..e8ba63f --- /dev/null +++ b/src/ExtCore.WebApplication/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,35 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.WebApplication.Extensions; + +/// +/// Contains the extension methods of the IApplicationBuilder interface. +/// +public static class ApplicationBuilderExtensions +{ + /// + /// Executes the Configure actions from all the extensions. It must be called inside the Configure method + /// of the web application's Startup class in order ExtCore to work properly. + /// + /// + /// The application builder passed to the Configure method of the web application's Startup class. + /// + public static void UseExtCore(this IApplicationBuilder applicationBuilder) + { + ILogger logger = applicationBuilder.ApplicationServices.GetService().CreateLogger("ExtCore.WebApplication"); + + foreach (IConfigureAction action in ExtensionManager.GetInstances().OrderBy(a => a.Priority)) + { + logger.LogInformation("Executing Configure action '{0}'", action.GetType().FullName); + action.Execute(applicationBuilder, applicationBuilder.ApplicationServices); + } + } +} \ No newline at end of file diff --git a/src/ExtCore.WebApplication/Extensions/ServiceCollectionExtensions.cs b/src/ExtCore.WebApplication/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..89573af --- /dev/null +++ b/src/ExtCore.WebApplication/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,106 @@ +// Copyright © 2017 Dmitry Sikorsky. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using ExtCore.Infrastructure; +using ExtCore.Infrastructure.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ExtCore.WebApplication.Extensions +{ + /// + /// Contains the extension methods of the IServiceCollection interface. + /// + public static class ServiceCollectionExtensions + { + /// + /// Discovers the assemblies and executes the ConfigureServices actions from all the extensions. + /// It must be called inside the ConfigureServices method of the web application's Startup class + /// in order ExtCore to work properly. + /// + /// + /// The services collection passed to the ConfigureServices method of the web application's Startup class. + /// + public static void AddExtCore(this IServiceCollection services) + { + services.AddExtCore(null); + } + + /// + /// Discovers the assemblies and executes the ConfigureServices actions from all the extensions. + /// It must be called inside the ConfigureServices method of the web application's Startup class + /// in order ExtCore to work properly. + /// + /// + /// The services collection passed to the ConfigureServices method of the web application's Startup class. + /// + /// The absolute extensions path. + public static void AddExtCore(this IServiceCollection services, string extensionsPath) + { + services.AddExtCore(extensionsPath, false, new DefaultAssemblyProvider(services.BuildServiceProvider())); + } + + /// + /// Discovers the assemblies and executes the ConfigureServices actions from all the extensions. + /// It must be called inside the ConfigureServices method of the web application's Startup class + /// in order ExtCore to work properly. + /// + /// + /// The services collection passed to the ConfigureServices method of the web application's Startup class. + /// + /// The absolute extensions path. + /// Determines whether the assemblies must be loaded from the subfolders recursively. + public static void AddExtCore(this IServiceCollection services, string extensionsPath, bool includingSubpaths) + { + services.AddExtCore(extensionsPath, includingSubpaths, new DefaultAssemblyProvider(services.BuildServiceProvider())); + } + + /// + /// Discovers the assemblies and executes the ConfigureServices actions from all the extensions. + /// It must be called inside the ConfigureServices method of the web application's Startup class + /// in order ExtCore to work properly. + /// + /// + /// The services collection passed to the ConfigureServices method of the web application's Startup class. + /// + /// The absolute extensions path. + /// The assembly provider that is used to discover and load the assemblies. + public static void AddExtCore(this IServiceCollection services, string extensionsPath, IAssemblyProvider assemblyProvider) + { + services.AddExtCore(extensionsPath, false, assemblyProvider); + } + + /// + /// Discovers the assemblies and executes the ConfigureServices actions from all the extensions. + /// It must be called inside the ConfigureServices method of the web application's Startup class + /// in order ExtCore to work properly. + /// + /// + /// The services collection passed to the ConfigureServices method of the web application's Startup class. + /// + /// The absolute extensions path. + /// Determines whether the assemblies must be loaded from the subfolders recursively. + /// The assembly provider that is used to discover and load the assemblies. + public static void AddExtCore(this IServiceCollection services, string extensionsPath, bool includingSubpaths, IAssemblyProvider assemblyProvider) + { + ServiceCollectionExtensions.DiscoverAssemblies(assemblyProvider, extensionsPath, includingSubpaths); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + ILogger logger = serviceProvider.GetService().CreateLogger("ExtCore.WebApplication"); + + foreach (IConfigureServicesAction action in ExtensionManager.GetInstances().OrderBy(a => a.Priority)) + { + logger.LogInformation("Executing ConfigureServices action '{0}'", action.GetType().FullName); + action.Execute(services, serviceProvider); + serviceProvider = services.BuildServiceProvider(); + } + } + + private static void DiscoverAssemblies(IAssemblyProvider assemblyProvider, string extensionsPath, bool includingSubpaths) + { + ExtensionManager.SetAssemblies(assemblyProvider.GetAssemblies(extensionsPath, includingSubpaths)); + } + } +} \ No newline at end of file diff --git a/src/ExtCore.WebApplication/IAssemblyProvider.cs b/src/ExtCore.WebApplication/IAssemblyProvider.cs index c795c0d..e2187dd 100644 --- a/src/ExtCore.WebApplication/IAssemblyProvider.cs +++ b/src/ExtCore.WebApplication/IAssemblyProvider.cs @@ -4,20 +4,24 @@ using System.Collections.Generic; using System.Reflection; -namespace ExtCore.WebApplication +namespace ExtCore.WebApplication; + +/// +/// Describes an assembly provider with the mechanism of getting assemblies that should be involved +/// in the ExtCore types discovering process. +/// +public interface IAssemblyProvider { /// - /// Describes an assembly provider with the mechanism of getting assemblies that should be involved - /// in the ExtCore types discovery process. + /// Discovers and then gets the discovered assemblies. /// - public interface IAssemblyProvider - { - /// - /// Discovers and then gets the discovered assemblies. - /// - /// The extensions path of a web application. Might be used or ignored - /// by an implementation of the IAssemblyProvider interface. - /// - IEnumerable GetAssemblies(string path); - } + /// The extensions path of a web application. Might be used or ignored + /// by an implementation of the IAssemblyProvider interface. + /// + /// Determines whether a web application will discover and then get the discovered assemblies from the subfolders + /// of a specific folder recursively. Might be used or ignored by an implementation of the + /// IAssemblyProvider interface. + /// + /// The discovered and loaded assemblies. + IEnumerable GetAssemblies(string path, bool includingSubpaths); } \ No newline at end of file diff --git a/src/ExtCore.WebApplication/Startup.cs b/src/ExtCore.WebApplication/Startup.cs deleted file mode 100644 index 4e3c29f..0000000 --- a/src/ExtCore.WebApplication/Startup.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright © 2015 Dmitry Sikorsky. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using ExtCore.Infrastructure; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace ExtCore.WebApplication -{ - /// - /// Represents default web application Startup class. Must be inherited in the derived web applications - /// in order ExtCore basic functionality to work. - /// - public abstract class Startup - { - protected IServiceProvider serviceProvider; - protected IAssemblyProvider assemblyProvider; - protected IConfigurationRoot configurationRoot; - protected ILogger logger; - - /// - /// Initializes a new instance of the Startup class. - /// - /// The service provider that is used to get different services from the DI. - public Startup(IServiceProvider serviceProvider) - : this(serviceProvider, new AssemblyProvider(serviceProvider)) - { - } - - /// - /// Initializes a new instance of the Startup class. - /// - /// The service provider that is used to get different services from the DI. - /// The assembly provider that is used to get the assemblies that should be involved in the ExtCore types discovery process. - public Startup(IServiceProvider serviceProvider, IAssemblyProvider assemblyProvider) - { - this.serviceProvider = serviceProvider; - this.assemblyProvider = assemblyProvider; - this.logger = serviceProvider.GetService().CreateLogger(); - } - - /// - /// Discovers the assemblies, initializes extensions and services that are used by a web application. - /// Also this method executes prioritized (defined in a specific order) actions (code fragments) - /// from all the extensions, so each extension may execute some code within this method using the - /// ConfigureServicesActionsByPriorities property of the IExtension interface. - /// - /// The current set of the services configured in the DI. - public virtual void ConfigureServices(IServiceCollection services) - { - this.DiscoverAssemblies(); - - foreach (IExtension extension in ExtensionManager.Extensions) - { - extension.SetServiceProvider(this.serviceProvider); - extension.SetConfigurationRoot(this.configurationRoot); - } - - foreach (Action prioritizedConfigureServicesAction in this.GetPrioritizedConfigureServicesActions()) - { - this.logger.LogInformation("Executing prioritized ConfigureServices action '{0}' of {1}", this.GetActionMethodInfo(prioritizedConfigureServicesAction)); - prioritizedConfigureServicesAction(services); - this.RebuildServiceProvider(services); - } - } - - /// - /// Executes prioritized (defined in a specific order) actions (code fragments) - /// from all the extensions, so each extension may execute some code within this method using the - /// ConfigureActionsByPriorities property of the IExtension interface. - /// - /// - public virtual void Configure(IApplicationBuilder applicationBuilder) - { - foreach (Action prioritizedConfigureAction in this.GetPrioritizedConfigureActions()) - { - this.logger.LogInformation("Executing prioritized Configure action '{0}' of {1}", this.GetActionMethodInfo(prioritizedConfigureAction)); - prioritizedConfigureAction(applicationBuilder); - } - } - - private void DiscoverAssemblies() - { - string extensionsPath = this.configurationRoot?["Extensions:Path"]; - IEnumerable assemblies = this.assemblyProvider.GetAssemblies( - string.IsNullOrEmpty(extensionsPath) ? - null : this.serviceProvider.GetService().ContentRootPath + extensionsPath - ); - - ExtensionManager.SetAssemblies(assemblies); - } - - private Action[] GetPrioritizedConfigureServicesActions() - { - List>> configureServicesActionsByPriorities = new List>>(); - - foreach (IExtension extension in ExtensionManager.Extensions) - if (extension.ConfigureServicesActionsByPriorities != null) - configureServicesActionsByPriorities.AddRange(extension.ConfigureServicesActionsByPriorities); - - return this.GetPrioritizedActions(configureServicesActionsByPriorities); - } - - private void RebuildServiceProvider(IServiceCollection services) - { - this.serviceProvider = services.BuildServiceProvider(); - - foreach (IExtension extension in ExtensionManager.Extensions) - extension.SetServiceProvider(this.serviceProvider); - } - - private Action[] GetPrioritizedConfigureActions() - { - List>> configureActionsByPriorities = new List>>(); - - foreach (IExtension extension in ExtensionManager.Extensions) - if (extension.ConfigureActionsByPriorities != null) - configureActionsByPriorities.AddRange(extension.ConfigureActionsByPriorities); - - return this.GetPrioritizedActions(configureActionsByPriorities); - } - - private Action[] GetPrioritizedActions(IEnumerable>> actionsByPriorities) - { - return actionsByPriorities - .OrderBy(actionByPriority => actionByPriority.Key) - .Select(actionByPriority => actionByPriority.Value) - .ToArray(); - } - - private string[] GetActionMethodInfo(Action action) - { - MethodInfo methodInfo = action.GetMethodInfo(); - - return new string[] { methodInfo.Name, methodInfo.DeclaringType.FullName }; - } - } -} \ No newline at end of file diff --git a/src/ExtCore.WebApplication/icon.png b/src/ExtCore.WebApplication/icon.png new file mode 100644 index 0000000..658aadf Binary files /dev/null and b/src/ExtCore.WebApplication/icon.png differ diff --git a/src/common.props b/src/common.props new file mode 100644 index 0000000..6c58c1a --- /dev/null +++ b/src/common.props @@ -0,0 +1,19 @@ + + + + Dmitry Sikorsky + Copyright © 2015 Dmitry Sikorsky + 9.0.0 + latest + icon.png + https://extcore.net + Apache-2.0 + git + https://github.com/ExtCore/ExtCore + + + + + + + \ No newline at end of file