diff --git a/.vs/config/applicationhost.config b/.vs/config/applicationhost.config new file mode 100644 index 00000000000..20dc0fd6fb1 --- /dev/null +++ b/.vs/config/applicationhost.config @@ -0,0 +1,1036 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 00000000000..c33f0ccb5bf --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/OrchardVNext.sln b/OrchardVNext.sln index 70c7ba9f926..aa8ee454e62 100644 --- a/OrchardVNext.sln +++ b/OrchardVNext.sln @@ -1,9 +1,8 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.22512.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Web", "src\OrchardVNext.Web\OrchardVNext.Web.kproj", "{07D91819-4C81-434F-8DE4-51D2B5A4EDA8}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Web", "src\OrchardVNext.Web\OrchardVNext.Web.xproj", "{07D91819-4C81-434F-8DE4-51D2B5A4EDA8}" ProjectSection(ProjectDependencies) = postProject {60B5B61F-09C1-4F5C-8A5A-953574D0DCA2} = {60B5B61F-09C1-4F5C-8A5A-953574D0DCA2} EndProjectSection @@ -12,25 +11,30 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject global.json = global.json EndProjectSection + ProjectSection(FolderGlobals) = preProject + global_1json__JSONSchema = http://json.schemastore.org/global + EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Framework", "src\OrchardVNext\OrchardVNext.Framework.kproj", "{60B5B61F-09C1-4F5C-8A5A-953574D0DCA2}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Framework", "src\OrchardVNext\OrchardVNext.Framework.xproj", "{60B5B61F-09C1-4F5C-8A5A-953574D0DCA2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{90030E85-0C4F-456F-B879-443E8A3F220D}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Test1", "src\OrchardVNext.Web\Modules\OrchardVNext.Test1\OrchardVNext.Test1.kproj", "{44CF51FB-7C14-4471-B113-9286BD41A783}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Test1", "src\OrchardVNext.Web\Modules\OrchardVNext.Test1\OrchardVNext.Test1.xproj", "{44CF51FB-7C14-4471-B113-9286BD41A783}" ProjectSection(ProjectDependencies) = postProject {60B5B61F-09C1-4F5C-8A5A-953574D0DCA2} = {60B5B61F-09C1-4F5C-8A5A-953574D0DCA2} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{275E087F-A4E2-4A7B-A924-ED68E3A52086}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Test2", "src\OrchardVNext.Web\Modules\OrchardVNext.Test2\OrchardVNext.Test2.kproj", "{C0BA112A-FD23-451B-8876-912E1AEAD87B}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Test2", "src\OrchardVNext.Web\Modules\OrchardVNext.Test2\OrchardVNext.Test2.xproj", "{C0BA112A-FD23-451B-8876-912E1AEAD87B}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Demo", "src\OrchardVNext.Web\Modules\OrchardVNext.Demo\OrchardVNext.Demo.kproj", "{19089E81-CEFD-4008-A31C-541D82F58C54}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Demo", "src\OrchardVNext.Web\Modules\OrchardVNext.Demo\OrchardVNext.Demo.xproj", "{19089E81-CEFD-4008-A31C-541D82F58C54}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C362E36F-3F0F-40B3-8BBF-2B0252E77A34}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Tests", "src\OrchardVNext.Tests\OrchardVNext.Tests.kproj", "{B8431B91-F908-416A-A82C-B29528FCA4E9}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Tests", "src\OrchardVNext.Tests\OrchardVNext.Tests.xproj", "{B8431B91-F908-416A-A82C-B29528FCA4E9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OrchardVNext.Core", "src\OrchardVNext.Web\Core\OrchardVNext.Core\OrchardVNext.Core.xproj", "{D2B2A745-17BA-4B5F-8992-93EA803BD068}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -62,6 +66,10 @@ Global {B8431B91-F908-416A-A82C-B29528FCA4E9}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8431B91-F908-416A-A82C-B29528FCA4E9}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8431B91-F908-416A-A82C-B29528FCA4E9}.Release|Any CPU.Build.0 = Release|Any CPU + {D2B2A745-17BA-4B5F-8992-93EA803BD068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2B2A745-17BA-4B5F-8992-93EA803BD068}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2B2A745-17BA-4B5F-8992-93EA803BD068}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2B2A745-17BA-4B5F-8992-93EA803BD068}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,5 +83,6 @@ Global {19089E81-CEFD-4008-A31C-541D82F58C54} = {90030E85-0C4F-456F-B879-443E8A3F220D} {C362E36F-3F0F-40B3-8BBF-2B0252E77A34} = {275E087F-A4E2-4A7B-A924-ED68E3A52086} {B8431B91-F908-416A-A82C-B29528FCA4E9} = {C362E36F-3F0F-40B3-8BBF-2B0252E77A34} + {D2B2A745-17BA-4B5F-8992-93EA803BD068} = {275E087F-A4E2-4A7B-A924-ED68E3A52086} EndGlobalSection EndGlobal diff --git a/OrchardVNext.sublime-project b/OrchardVNext.sublime-project new file mode 100644 index 00000000000..472baf8a620 --- /dev/null +++ b/OrchardVNext.sublime-project @@ -0,0 +1,10 @@ +{ + "folders": + [ + { + "follow_symlinks": true, + "path": "." + } + ], + "solution_file": "./OrchardVNext.sln" +} \ No newline at end of file diff --git a/OrchardVNext.sublime-workspace b/OrchardVNext.sublime-workspace new file mode 100644 index 00000000000..c08d9490367 --- /dev/null +++ b/OrchardVNext.sublime-workspace @@ -0,0 +1,867 @@ +{ + "auto_complete": + { + "selected_items": + [ + ] + }, + "buffers": + [ + { + "file": "src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs", + "settings": + { + "buffer_size": 541, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs", + "settings": + { + "buffer_size": 626, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/project.json", + "settings": + { + "buffer_size": 1139, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/InvokeExtensions.cs", + "settings": + { + "buffer_size": 1987, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext.Web/Startup.cs", + "settings": + { + "buffer_size": 281, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/Environment/OrchardStarter.cs", + "settings": + { + "buffer_size": 4096, + "line_ending": "Windows" + } + }, + { + "contents": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing Microsoft.Framework.ConfigurationModel;\nusing OrchardVNext.Environment.Configuration.Sources;\nusing OrchardVNext.FileSystems.AppData;\n\nnamespace OrchardVNext.Environment.Configuration {\n public interface IShellSettingsManager {\n IEnumerable LoadSettings();\n }\n\n public class ShellSettingsManager : IShellSettingsManager {\n private readonly IAppDataFolder _appDataFolder;\n private const string _settingsFileNameFormat = \"Settings.{0}\";\n private readonly string[] _settingFileNameExtensions = new string[] { \"txt\", \"json\" };\n\n public ShellSettingsManager(IAppDataFolder appDataFolder) {\n _appDataFolder = appDataFolder;\n }\n\n IEnumerable IShellSettingsManager.LoadSettings() {\n var filePaths = _appDataFolder\n .ListDirectories(\"Sites\")\n .SelectMany(path => _appDataFolder.ListFiles(path))\n .Where(path => {\n var filePathName = Path.GetFileName(path);\n\n return _settingFileNameExtensions.Any(p =>\n string.Equals(filePathName, string.Format(_settingsFileNameFormat, p), StringComparison.OrdinalIgnoreCase)\n );\n });\n\n List shellSettings = new List();\n\n foreach (var filePath in filePaths) {\n IConfigurationSourceContainer configurationContainer = null;\n\n var extension = Path.GetExtension(filePath);\n\n switch (extension) {\n case \".json\":\n configurationContainer = new Microsoft.Framework.ConfigurationModel.Configuration()\n .AddJsonFile(filePath);\n break;\n case \".xml\":\n configurationContainer = new Microsoft.Framework.ConfigurationModel.Configuration()\n .AddXmlFile(filePath);\n break;\n case \".ini\":\n configurationContainer = new Microsoft.Framework.ConfigurationModel.Configuration()\n .AddIniFile(filePath);\n break;\n case \".txt\":\n configurationContainer = new Microsoft.Framework.ConfigurationModel.Configuration()\n .Add(new DefaultFileConfigurationSource(_appDataFolder, filePath));\n break;\n }\n\n if (configurationContainer != null) {\n var shellSetting = new ShellSettings {\n Name = configurationContainer.Get(\"Name\"),\n DataConnectionString = configurationContainer.Get(\"DataConnectionString\"),\n DataProvider = configurationContainer.Get(\"DataProvider\"),\n DataTablePrefix = configurationContainer.Get(\"DataTablePrefix\"),\n RequestUrlHost = configurationContainer.Get(\"RequestUrlHost\"),\n RequestUrlPrefix = configurationContainer.Get(\"RequestUrlPrefix\")\n };\n\n TenantState state;\n shellSetting.State = Enum.TryParse(configurationContainer.Get(\"State\"), true, out state) ? state : TenantState.Uninitialized;\n\n shellSettings.Add(shellSetting);\n }\n }\n\n return shellSettings;\n }\n }\n}", + "file": "src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs", + "file_size": 3723, + "file_write_time": 130711908279920244, + "settings": + { + "buffer_size": 3643, + "line_ending": "Windows" + } + }, + { + "file": "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/C#.sublime-settings", + "settings": + { + "buffer_size": 163, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/FileSystems/AppData/AppDataFolder.cs", + "settings": + { + "buffer_size": 6615, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/FileSystems/AppData/IAppDataFolderRoot.cs", + "settings": + { + "buffer_size": 948, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs", + "settings": + { + "buffer_size": 3951, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs", + "settings": + { + "buffer_size": 5420, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/Environment/ShellBuilders/Models/ShellBlueprint.cs", + "settings": + { + "buffer_size": 1228, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/ContentManagement/ContentExtensions.cs", + "settings": + { + "buffer_size": 1641, + "line_ending": "Windows" + } + }, + { + "file": "src/OrchardVNext/ContentManagement/IContent.cs", + "settings": + { + "buffer_size": 235, + "line_ending": "Windows" + } + }, + { + "file": "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/csharp.sublime-build", + "settings": + { + "buffer_size": 79, + "line_ending": "Windows", + "name": "untitled.sublime-build" + } + }, + { + "file": "src/OrchardVNext/Environment/DefaultOrchardHost.cs", + "settings": + { + "buffer_size": 704, + "line_ending": "Windows" + } + }, + { + "file": "OrchardVNext.sublime-project", + "settings": + { + "buffer_size": 152, + "line_ending": "Windows" + } + } + ], + "build_system": "", + "command_palette": + { + "height": 119.0, + "selected_items": + [ + [ + "Omnisharp Reli", + "OmniSharpSublime: Reload Solution" + ], + [ + "OmniSharpSublime: Re", + "OmniSharpSublime: Reload Solution" + ] + ], + "width": 588.0 + }, + "console": + { + "height": 651.0, + "history": + [ + "install", + "import urllib.request,os,hashlib; h = 'eb2297e1a458f27d836c04bb0cbaf282' + 'd0e7a3098092775ccb37ca9d6b2e4b7d'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by) ", + "install", + "import urllib.request,os,hashlib; h = 'eb2297e1a458f27d836c04bb0cbaf282' + 'd0e7a3098092775ccb37ca9d6b2e4b7d'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by) " + ] + }, + "distraction_free": + { + "menu_visible": true, + "show_minimap": false, + "show_open_files": false, + "show_tabs": false, + "side_bar_visible": false, + "status_bar_visible": false + }, + "expanded_folders": + [ + "/D/Brochard", + "/D/Brochard/src", + "/D/Brochard/src/OrchardVNext", + "/D/Brochard/src/OrchardVNext/ContentManagement", + "/D/Brochard/src/OrchardVNext.Web" + ], + "file_history": + [ + "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/Default/Default (Windows).sublime-keymap", + "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/Default (Windows).sublime-keymap", + "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/C#.sublime-settings" + ], + "find": + { + "height": 31.0 + }, + "find_in_files": + { + "height": 138.0, + "where_history": + [ + ] + }, + "find_state": + { + "case_sensitive": false, + "find_history": + [ + "ctrl+s\"", + "ctrl+s" + ], + "highlight": true, + "in_selection": false, + "preserve_case": false, + "regex": false, + "replace_history": + [ + ], + "reverse": false, + "show_context": true, + "use_buffer2": true, + "whole_word": false, + "wrap": true + }, + "groups": + [ + { + "selected": 6, + "sheets": + [ + { + "buffer": 0, + "file": "src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 541, + "regions": + { + }, + "selection": + [ + [ + 490, + 490 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 17, + "type": "text" + }, + { + "buffer": 1, + "file": "src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 626, + "regions": + { + }, + "selection": + [ + [ + 507, + 507 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 15, + "type": "text" + }, + { + "buffer": 2, + "file": "src/OrchardVNext/project.json", + "semi_transient": false, + "settings": + { + "buffer_size": 1139, + "regions": + { + }, + "selection": + [ + [ + 933, + 933 + ] + ], + "settings": + { + "syntax": "Packages/JavaScript/JSON.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 13, + "type": "text" + }, + { + "buffer": 3, + "file": "src/OrchardVNext/InvokeExtensions.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 1987, + "regions": + { + }, + "selection": + [ + [ + 559, + 559 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 132.0, + "zoom_level": 1.0 + }, + "stack_index": 3, + "type": "text" + }, + { + "buffer": 4, + "file": "src/OrchardVNext.Web/Startup.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 281, + "regions": + { + }, + "selection": + [ + [ + 219, + 219 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 12, + "type": "text" + }, + { + "buffer": 5, + "file": "src/OrchardVNext/Environment/OrchardStarter.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 4096, + "regions": + { + }, + "selection": + [ + [ + 1628, + 1628 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 178.0, + "zoom_level": 1.0 + }, + "stack_index": 2, + "type": "text" + }, + { + "buffer": 6, + "file": "src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 3643, + "regions": + { + }, + "selection": + [ + [ + 1878, + 1878 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 321.0, + "zoom_level": 1.0 + }, + "stack_index": 0, + "type": "text" + }, + { + "buffer": 7, + "file": "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/C#.sublime-settings", + "semi_transient": false, + "settings": + { + "buffer_size": 163, + "regions": + { + }, + "selection": + [ + [ + 2, + 2 + ] + ], + "settings": + { + "syntax": "Packages/JavaScript/JSON.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 1, + "type": "text" + }, + { + "buffer": 8, + "file": "src/OrchardVNext/FileSystems/AppData/AppDataFolder.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 6615, + "regions": + { + }, + "selection": + [ + [ + 378, + 378 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 5, + "type": "text" + }, + { + "buffer": 9, + "file": "src/OrchardVNext/FileSystems/AppData/IAppDataFolderRoot.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 948, + "regions": + { + }, + "selection": + [ + [ + 252, + 252 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 4, + "type": "text" + }, + { + "buffer": 10, + "file": "src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 3951, + "regions": + { + }, + "selection": + [ + [ + 2026, + 2026 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 6, + "type": "text" + }, + { + "buffer": 11, + "file": "src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 5420, + "regions": + { + }, + "selection": + [ + [ + 1978, + 1978 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 11, + "type": "text" + }, + { + "buffer": 12, + "file": "src/OrchardVNext/Environment/ShellBuilders/Models/ShellBlueprint.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 1228, + "regions": + { + }, + "selection": + [ + [ + 580, + 580 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 10, + "type": "text" + }, + { + "buffer": 13, + "file": "src/OrchardVNext/ContentManagement/ContentExtensions.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 1641, + "regions": + { + }, + "selection": + [ + [ + 722, + 722 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 9, + "type": "text" + }, + { + "buffer": 14, + "file": "src/OrchardVNext/ContentManagement/IContent.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 235, + "regions": + { + }, + "selection": + [ + [ + 225, + 225 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 8, + "type": "text" + }, + { + "buffer": 15, + "file": "/C/Users/Nicholas/AppData/Roaming/Sublime Text 3/Packages/User/csharp.sublime-build", + "semi_transient": false, + "settings": + { + "buffer_size": 79, + "regions": + { + }, + "selection": + [ + [ + 63, + 63 + ] + ], + "settings": + { + "syntax": "Packages/JavaScript/JSON.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 7, + "type": "text" + }, + { + "buffer": 16, + "file": "src/OrchardVNext/Environment/DefaultOrchardHost.cs", + "semi_transient": false, + "settings": + { + "buffer_size": 704, + "regions": + { + }, + "selection": + [ + [ + 628, + 628 + ] + ], + "settings": + { + "syntax": "Packages/C#/C#.tmLanguage", + "tab_size": 4, + "translate_tabs_to_spaces": true + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 14, + "type": "text" + }, + { + "buffer": 17, + "file": "OrchardVNext.sublime-project", + "semi_transient": false, + "settings": + { + "buffer_size": 152, + "regions": + { + }, + "selection": + [ + [ + 0, + 0 + ] + ], + "settings": + { + "syntax": "Packages/JavaScript/JSON.tmLanguage" + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 16, + "type": "text" + } + ] + } + ], + "incremental_find": + { + "height": 30.0 + }, + "input": + { + "height": 42.0 + }, + "layout": + { + "cells": + [ + [ + 0, + 0, + 1, + 1 + ] + ], + "cols": + [ + 0.0, + 1.0 + ], + "rows": + [ + 0.0, + 1.0 + ] + }, + "menu_visible": true, + "output.exec": + { + "height": 596.0 + }, + "output.find_results": + { + "height": 0.0 + }, + "output.variable_get": + { + "height": 498.0 + }, + "project": "OrchardVNext.sublime-project", + "replace": + { + "height": 58.0 + }, + "save_all_on_build": true, + "select_file": + { + "height": 0.0, + "selected_items": + [ + ], + "width": 0.0 + }, + "select_project": + { + "height": 0.0, + "selected_items": + [ + ], + "width": 0.0 + }, + "select_symbol": + { + "height": 0.0, + "selected_items": + [ + ], + "width": 0.0 + }, + "selected_group": 0, + "settings": + { + }, + "show_minimap": true, + "show_open_files": false, + "show_tabs": true, + "side_bar_visible": true, + "side_bar_width": 326.0, + "status_bar_visible": true, + "template_settings": + { + } +} diff --git a/global.json b/global.json index cad39504d41..d94610fe3bd 100644 --- a/global.json +++ b/global.json @@ -1,3 +1,3 @@ { - "sources": [ "src" ] + "projects": [ "src" ] } diff --git a/src/OrchardVNext.Tests/OrchardVNext.Tests.kproj b/src/OrchardVNext.Tests/OrchardVNext.Tests.xproj similarity index 85% rename from src/OrchardVNext.Tests/OrchardVNext.Tests.kproj rename to src/OrchardVNext.Tests/OrchardVNext.Tests.xproj index 1cbad2fde37..2cbc5225c0f 100644 --- a/src/OrchardVNext.Tests/OrchardVNext.Tests.kproj +++ b/src/OrchardVNext.Tests/OrchardVNext.Tests.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + b8431b91-f908-416a-a82c-b29528fca4e9 OrchardVNext.Tests @@ -17,7 +17,7 @@ 2.0 - + diff --git a/src/OrchardVNext.Tests/project.json b/src/OrchardVNext.Tests/project.json index fabea333402..22f2aa11e3c 100644 --- a/src/OrchardVNext.Tests/project.json +++ b/src/OrchardVNext.Tests/project.json @@ -3,19 +3,18 @@ "warningsAsErrors": true }, "dependencies": { - "xunit.runner.kre": "1.0.0-beta4-*", - "Microsoft.AspNet.Testing": "1.0.0-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*", "OrchardVNext": "" }, "commands": { - "test": "xunit.runner.kre" + "test": "xunit.runner.aspnet" }, "frameworks": { - "aspnet50": { + "dnx451": { "dependencies": { "Moq": "4.2.1312.1622" } }, - "aspnetcore50": { } + "dnxcore50": { } } } diff --git a/src/OrchardVNext/OrchardVNext.Framework.kproj b/src/OrchardVNext.Web/Core/OrchardVNext.Core/OrchardVNext.Core.xproj similarity index 70% rename from src/OrchardVNext/OrchardVNext.Framework.kproj rename to src/OrchardVNext.Web/Core/OrchardVNext.Core/OrchardVNext.Core.xproj index b9d4b9b3cee..c49d3263219 100644 --- a/src/OrchardVNext/OrchardVNext.Framework.kproj +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/OrchardVNext.Core.xproj @@ -4,19 +4,19 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + - 60b5b61f-09c1-4f5c-8a5a-953574d0dca2 + d2b2a745-17ba-4b5f-8992-93ea803bd068 Library - OrchardVNext + OrchardVNext.Core - OrchardVNext.Framework + OrchardVNext.Core 2.0 - + diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/ContentDefinitionManager.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/ContentDefinitionManager.cs new file mode 100644 index 00000000000..05548268936 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/ContentDefinitionManager.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData; +using OrchardVNext.ContentManagement.MetaData.Models; +using OrchardVNext.ContentManagement.MetaData.Services; +using OrchardVNext.Core.Settings.Metadata.Records; +using OrchardVNext.Data; + +namespace OrchardVNext.Core.Settings.Metadata { + public class ContentDefinitionManager : Component, IContentDefinitionManager { + private readonly IContentStorageProvider _contentStorageProvider; + private readonly ISettingsFormatter _settingsFormatter; + + public ContentDefinitionManager( + IContentStorageProvider contentStorageProvider, + ISettingsFormatter settingsFormatter) { + _contentStorageProvider = contentStorageProvider; + _settingsFormatter = settingsFormatter; + } + + public IEnumerable ListTypeDefinitions() { + return AcquireContentTypeDefinitions().Values; + } + + public IEnumerable ListPartDefinitions() { + return AcquireContentPartDefinitions().Values; + } + + public IEnumerable ListFieldDefinitions() { + return AcquireContentFieldDefinitions().Values; + } + + public ContentTypeDefinition GetTypeDefinition(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return null; + } + + var contentTypeDefinitions = AcquireContentTypeDefinitions(); + if (contentTypeDefinitions.ContainsKey(name)) { + return contentTypeDefinitions[name]; + } + + return null; + } + + public ContentPartDefinition GetPartDefinition(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return null; + } + + var contentPartDefinitions = AcquireContentPartDefinitions(); + if (contentPartDefinitions.ContainsKey(name)) { + return contentPartDefinitions[name]; + } + + return null; + } + + public void DeleteTypeDefinition(string name) { + var record = _contentStorageProvider + .Query(x => x.Name == name) + .SingleOrDefault(); + + // deletes the content type record associated + if (record != null) { + _contentStorageProvider.Remove(record); + } + } + + public void DeletePartDefinition(string name) { + // remove parts from current types + var typesWithPart = ListTypeDefinitions().Where(typeDefinition => typeDefinition.Parts.Any(part => part.PartDefinition.Name == name)); + + foreach (var typeDefinition in typesWithPart) { + this.AlterTypeDefinition(typeDefinition.Name, builder => builder.RemovePart(name)); + } + + // delete part + var record = _contentStorageProvider + .Query(x => x.Name == name) + .SingleOrDefault(); + + if (record != null) { + _contentStorageProvider.Remove(record); + } + } + + public void StoreTypeDefinition(ContentTypeDefinition contentTypeDefinition) { + Apply(contentTypeDefinition, Acquire(contentTypeDefinition)); + } + + public void StorePartDefinition(ContentPartDefinition contentPartDefinition) { + Apply(contentPartDefinition, Acquire(contentPartDefinition)); + } + + + private IDictionary AcquireContentTypeDefinitions() { + AcquireContentPartDefinitions(); + + var contentTypeDefinitionRecords = _contentStorageProvider + .Query() + .Select(Build); + + return contentTypeDefinitionRecords.ToDictionary(x => x.Name, y => y, StringComparer.OrdinalIgnoreCase); + } + + private IDictionary AcquireContentPartDefinitions() { + var contentPartDefinitionRecords = _contentStorageProvider + .Query() + .Select(Build); + + return contentPartDefinitionRecords.ToDictionary(x => x.Name, y => y, StringComparer.OrdinalIgnoreCase); + } + + private IDictionary AcquireContentFieldDefinitions() { + return _contentStorageProvider + .Query() + .Select(Build) + .ToDictionary(x => x.Name, y => y); + } + + private ContentTypeDefinitionRecord Acquire(ContentTypeDefinition contentTypeDefinition) { + var result = _contentStorageProvider + .Query(x => x.Name == contentTypeDefinition.Name) + .SingleOrDefault(); + + if (result == null) { + result = new ContentTypeDefinitionRecord { Name = contentTypeDefinition.Name, DisplayName = contentTypeDefinition.DisplayName }; + _contentStorageProvider.Store(result); + } + return result; + } + + private ContentPartDefinitionRecord Acquire(ContentPartDefinition contentPartDefinition) { + var result = _contentStorageProvider + .Query(x => x.Name == contentPartDefinition.Name) + .SingleOrDefault(); + + if (result == null) { + result = new ContentPartDefinitionRecord { Name = contentPartDefinition.Name }; + _contentStorageProvider.Store(result); + } + return result; + } + + private ContentFieldDefinitionRecord Acquire(ContentFieldDefinition contentFieldDefinition) { + var result = _contentStorageProvider + .Query(x => x.Name == contentFieldDefinition.Name) + .SingleOrDefault(); + + if (result == null) { + result = new ContentFieldDefinitionRecord { Name = contentFieldDefinition.Name }; + _contentStorageProvider.Store(result); + } + return result; + } + + private void Apply(ContentTypeDefinition model, ContentTypeDefinitionRecord record) { + record.DisplayName = model.DisplayName; + record.Settings = _settingsFormatter.Map(model.Settings).ToString(); + + var toRemove = record.ContentTypePartDefinitionRecords + .Where(partDefinitionRecord => model.Parts.All(part => partDefinitionRecord.ContentPartDefinitionRecord.Name != part.PartDefinition.Name)) + .ToList(); + + foreach (var remove in toRemove) { + record.ContentTypePartDefinitionRecords.Remove(remove); + } + + foreach (var part in model.Parts) { + var partName = part.PartDefinition.Name; + var typePartRecord = record.ContentTypePartDefinitionRecords.SingleOrDefault(r => r.ContentPartDefinitionRecord.Name == partName); + if (typePartRecord == null) { + typePartRecord = new ContentTypePartDefinitionRecord { ContentPartDefinitionRecord = Acquire(part.PartDefinition) }; + record.ContentTypePartDefinitionRecords.Add(typePartRecord); + } + Apply(part, typePartRecord); + } + } + + private void Apply(ContentTypePartDefinition model, ContentTypePartDefinitionRecord record) { + record.Settings = Compose(_settingsFormatter.Map(model.Settings)); + } + + private void Apply(ContentPartDefinition model, ContentPartDefinitionRecord record) { + record.Settings = _settingsFormatter.Map(model.Settings).ToString(); + + var toRemove = record.ContentPartFieldDefinitionRecords + .Where(partFieldDefinitionRecord => model.Fields.All(partField => partFieldDefinitionRecord.Name != partField.Name)) + .ToList(); + + foreach (var remove in toRemove) { + record.ContentPartFieldDefinitionRecords.Remove(remove); + } + + foreach (var field in model.Fields) { + var fieldName = field.Name; + var partFieldRecord = record.ContentPartFieldDefinitionRecords.SingleOrDefault(r => r.Name == fieldName); + if (partFieldRecord == null) { + partFieldRecord = new ContentPartFieldDefinitionRecord { + ContentFieldDefinitionRecord = Acquire(field.FieldDefinition), + Name = field.Name + }; + record.ContentPartFieldDefinitionRecords.Add(partFieldRecord); + } + Apply(field, partFieldRecord); + } + } + + private void Apply(ContentPartFieldDefinition model, ContentPartFieldDefinitionRecord record) { + record.Settings = Compose(_settingsFormatter.Map(model.Settings)); + } + + ContentTypeDefinition Build(ContentTypeDefinitionRecord source) { + return new ContentTypeDefinition( + source.Name, + source.DisplayName, + source.ContentTypePartDefinitionRecords.Select(Build), + _settingsFormatter.Map(Parse(source.Settings))); + } + + ContentTypePartDefinition Build(ContentTypePartDefinitionRecord source) { + return new ContentTypePartDefinition( + Build(source.ContentPartDefinitionRecord), + _settingsFormatter.Map(Parse(source.Settings))); + } + + ContentPartDefinition Build(ContentPartDefinitionRecord source) { + return new ContentPartDefinition( + source.Name, + source.ContentPartFieldDefinitionRecords.Select(Build), + _settingsFormatter.Map(Parse(source.Settings))); + } + + ContentPartFieldDefinition Build(ContentPartFieldDefinitionRecord source) { + return new ContentPartFieldDefinition( + Build(source.ContentFieldDefinitionRecord), + source.Name, + _settingsFormatter.Map(Parse(source.Settings))); + } + + ContentFieldDefinition Build(ContentFieldDefinitionRecord source) { + return new ContentFieldDefinition(source.Name); + } + + XElement Parse(string settings) { + if (string.IsNullOrEmpty(settings)) + return null; + + try { + return XElement.Parse(settings); + } + catch (Exception ex) { + Logger.Error(ex, "Unable to parse settings xml"); + return null; + } + } + + static string Compose(XElement map) { + return map?.ToString(); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs new file mode 100644 index 00000000000..81e43bc3cd5 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs @@ -0,0 +1,13 @@ +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Records; +using OrchardVNext.Data; + +namespace OrchardVNext.Core.Settings.Metadata.Records { + [Persistent] + public class ContentFieldDefinitionRecord : DocumentRecord { + public string Name { + get { return this.RetrieveValue(x => x.Name); } + set { this.StoreValue(x => x.Name, value); } + } + } +} diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs new file mode 100644 index 00000000000..49f4bc05258 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Records; +using OrchardVNext.Data; +using OrchardVNext.Data.Conventions; + +namespace OrchardVNext.Core.Settings.Metadata.Records { + [Persistent] + public class ContentPartDefinitionRecord : DocumentRecord { + public ContentPartDefinitionRecord() { + ContentPartFieldDefinitionRecords = new List(); + } + + public string Name { + get { return this.RetrieveValue(x => x.Name); } + set { this.StoreValue(x => x.Name, value); } + } + public bool Hidden { + get { return this.RetrieveValue(x => x.Hidden); } + set { this.StoreValue(x => x.Hidden, value); } + } + [StringLengthMax] + public string Settings { + get { return this.RetrieveValue(x => x.Settings); } + set { this.StoreValue(x => x.Settings, value); } + } + + //[CascadeAllDeleteOrphan] + public virtual IList ContentPartFieldDefinitionRecords { get; set; } + + } +} diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs new file mode 100644 index 00000000000..ac0b25a9c61 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs @@ -0,0 +1,13 @@ +using OrchardVNext.Data; +using OrchardVNext.Data.Conventions; + +namespace OrchardVNext.Core.Settings.Metadata.Records { + [Persistent] + public class ContentPartFieldDefinitionRecord { + public virtual int Id { get; set; } + public virtual ContentFieldDefinitionRecord ContentFieldDefinitionRecord { get; set; } + public virtual string Name { get; set; } + [StringLengthMax] + public virtual string Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs new file mode 100644 index 00000000000..ab1c566a231 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Records; +using OrchardVNext.Data; +using OrchardVNext.Data.Conventions; + +namespace OrchardVNext.Core.Settings.Metadata.Records { + [Persistent] + public class ContentTypeDefinitionRecord : DocumentRecord { + public ContentTypeDefinitionRecord() { + ContentTypePartDefinitionRecords = new List(); + } + + public string Name { + get { return this.RetrieveValue(x => x.Name); } + set { this.StoreValue(x => x.Name, value); } + } + public string DisplayName { + get { return this.RetrieveValue(x => x.DisplayName); } + set { this.StoreValue(x => x.DisplayName, value); } + } + public bool Hidden { + get { return this.RetrieveValue(x => x.Hidden); } + set { this.StoreValue(x => x.Hidden, value); } + } + [StringLengthMax] + public string Settings { + get { return this.RetrieveValue(x => x.Settings); } + set { this.StoreValue(x => x.Settings, value); } + } + + //[CascadeAllDeleteOrphan] + public virtual IList ContentTypePartDefinitionRecords { get; set; } + } + +} diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs new file mode 100644 index 00000000000..a8d31e161d0 --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs @@ -0,0 +1,12 @@ +using OrchardVNext.Data; +using OrchardVNext.Data.Conventions; + +namespace OrchardVNext.Core.Settings.Metadata.Records { + [Persistent] + public class ContentTypePartDefinitionRecord { + public virtual int Id { get; set; } + public virtual ContentPartDefinitionRecord ContentPartDefinitionRecord { get; set; } + [StringLengthMax] + public virtual string Settings { get; set; } + } +} diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Module.txt b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Module.txt new file mode 100644 index 00000000000..23235dc07ea --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/Settings/Module.txt @@ -0,0 +1,9 @@ +Name: Settings +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 2.0.x +OrchardVersion: 2.0.x +Description: The settings module creates site settings that other modules can contribute to. +FeatureDescription: Site settings. +Category: Core diff --git a/src/OrchardVNext.Web/Core/OrchardVNext.Core/project.json b/src/OrchardVNext.Web/Core/OrchardVNext.Core/project.json new file mode 100644 index 00000000000..a4e2a7030fb --- /dev/null +++ b/src/OrchardVNext.Web/Core/OrchardVNext.Core/project.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0-*", + "dependencies": { + "Microsoft.AspNet.Mvc": "6.0.0-beta4-*", + "OrchardVNext": "" + }, + "compilationOptions": { "define": [ "TRACE" ], "warningsAsErrors": true }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + } +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Controllers/HomeController.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Controllers/HomeController.cs index 5389e38c4ae..7f726953e2d 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Controllers/HomeController.cs +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Controllers/HomeController.cs @@ -1,16 +1,62 @@ using Microsoft.AspNet.Mvc; +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Handlers; +using OrchardVNext.Data; +using OrchardVNext.Demo.Models; using OrchardVNext.Test1; namespace OrchardVNext.Demo.Controllers { public class HomeController : Controller { private readonly ITestDependency _testDependency; + private readonly IContentStorageProvider _contentStorageProvider; + private readonly IContentManager _contentManager; - public HomeController(ITestDependency testDependency) { + public HomeController(ITestDependency testDependency, + IContentStorageProvider contentStorageProvider, + IContentManager contentManager) { _testDependency = testDependency; + _contentStorageProvider = contentStorageProvider; + _contentManager = contentManager; + } + + public ActionResult Index() + { + //var contentItem = new ContentItem + //{ + // VersionRecord = new ContentItemVersionRecord + // { + // ContentItemRecord = new ContentItemRecord(), + // Number = 1, + // Latest = true, + // Published = true + // } + //}; + + //contentItem.VersionRecord.ContentItemRecord.Versions.Add(contentItem.VersionRecord); + + //_contentStorageProvider.Store(contentItem); + + //var indexedRecordIds = _contentIndexProvider.GetByFilter(x => x.Id == 1); + + //var retrievedRecord = _contentStorageProvider.Get(contentItem.Id); + + //var indexedRetrievedRecords = _contentStorageProvider.GetMany(x => x.Id == 1); + + + var contentItem = _contentManager.New("Foo"); + contentItem.As().Line = "Orchard VNext Rocks"; + _contentManager.Create(contentItem); + + var retrieveContentItem = _contentManager.Get(contentItem.Id); + var lineToSay = retrieveContentItem.As().Line; + + return View("Index", _testDependency.SayHi(lineToSay)); } + } - public ActionResult Index() { - return View("Index", _testDependency.SayHi()); + public class TestContentPartAHandler : ContentHandlerBase { + public override void Activating(ActivatingContentContext context) { + context.Builder.Weld(); } } } \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartA.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartA.cs new file mode 100644 index 00000000000..130fd7b7511 --- /dev/null +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartA.cs @@ -0,0 +1,11 @@ +using System; +using OrchardVNext.ContentManagement; + +namespace OrchardVNext.Demo.Models { + public class TestContentPartA : ContentPart { + public string Line { + get { return this.Retrieve(x => x.Line); } + set { this.Store(x => x.Line, value); } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartB.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartB.cs new file mode 100644 index 00000000000..0770aff1e7c --- /dev/null +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Models/TestContentPartB.cs @@ -0,0 +1,10 @@ +using OrchardVNext.ContentManagement; + +namespace OrchardVNext.Demo.Models { + public class TestContentPartB : ContentPart { + public int Line { + get { return this.Retrieve(x => x.Line); } + set { this.Store(x => x.Line, value); } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.kproj b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.xproj similarity index 74% rename from src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.kproj rename to src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.xproj index 0ce34893434..3c4038a2967 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.kproj +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/OrchardVNext.Demo.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + 19089e81-cefd-4008-a31c-541d82f58c54 OrchardVNext.Demo @@ -18,10 +18,5 @@ 2.0 3157 - - - - - - + \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Properties/debugSettings.json b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Properties/debugSettings.json new file mode 100644 index 00000000000..a44fad34a3d --- /dev/null +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Properties/debugSettings.json @@ -0,0 +1,3 @@ +{ + "Profiles": [] +} \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Routes.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Routes.cs index cddd544cf1f..6a0683d6189 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Routes.cs +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/Routes.cs @@ -1,13 +1,8 @@ -using OrchardVNext.Environment.Configuration; +using System.Collections.Generic; using OrchardVNext.Mvc.Routes; -using System.Collections.Generic; namespace OrchardVNext.Demo { public class Routes : IRouteProvider { - public readonly ShellSettings _shellSettings; - public Routes(ShellSettings shellSettings) { - _shellSettings = shellSettings; - } public IEnumerable GetRoutes() { return new[] { new RouteDescriptor { diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/project.json b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/project.json index 54b651730cd..1f2372261dd 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/project.json +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Demo/project.json @@ -1,11 +1,11 @@ { "dependencies": { - "Microsoft.AspNet.Mvc": "6.0.0-beta4-*", + "Microsoft.AspNet.Mvc": "6.0.0-beta5-*", "OrchardVNext": "", "OrchardVNext.Test1": "" }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "dnx451": { }, + "dnxcore50": { } } } \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs index 03d9f2a3bba..2294b824585 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/Class.cs @@ -1,9 +1,9 @@ -using System; +using System; using OrchardVNext.Environment.Configuration; namespace OrchardVNext.Test1 { public interface ITestDependency : IDependency { - string SayHi(); + string SayHi(string line); } public class Class : ITestDependency { @@ -12,8 +12,8 @@ public Class(ShellSettings shellSettings) { _shellSettings = shellSettings; } - public string SayHi() { - return string.Format("Hi from tenant {0}", _shellSettings.Name); + public string SayHi(string line) { + return string.Format("Hi from tenant {0} - {1}", _shellSettings.Name, line); } } } \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.kproj b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.xproj similarity index 74% rename from src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.kproj rename to src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.xproj index 747d33c4379..e2384970781 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.kproj +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/OrchardVNext.Test1.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + 44cf51fb-7c14-4471-b113-9286bd41a783 OrchardVNext.Test1 @@ -18,10 +18,5 @@ 2.0 49881 - - - - - - + \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs index 38ef64d29e4..38a014b703d 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/TestMiddleware.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using OrchardVNext.Middleware; using System; diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/project.json b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/project.json index 7c276bf9c82..d6f1b6fec34 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/project.json +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test1/project.json @@ -1,10 +1,9 @@ { "dependencies": { - "Microsoft.AspNet.Server.IIS": "1.0.0-beta4-*", "OrchardVNext": "" }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "dnx451": { }, + "dnxcore50": { } } } \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.kproj b/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.xproj similarity index 74% rename from src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.kproj rename to src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.xproj index 73132fce431..511c1497d07 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.kproj +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/OrchardVNext.Test2.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + c0ba112a-fd23-451b-8876-912e1aead87b OrchardVNext.Test2 @@ -18,10 +18,5 @@ 2.0 13411 - - - - - - + \ No newline at end of file diff --git a/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/project.json b/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/project.json index 7c276bf9c82..60664ae7ef4 100644 --- a/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/project.json +++ b/src/OrchardVNext.Web/Modules/OrchardVNext.Test2/project.json @@ -1,10 +1,10 @@ { "dependencies": { - "Microsoft.AspNet.Server.IIS": "1.0.0-beta4-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-beta5-*", "OrchardVNext": "" }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "dnx451": { }, + "dnxcore50": { } } } \ No newline at end of file diff --git a/src/OrchardVNext.Web/OrchardVNext.Web.kproj b/src/OrchardVNext.Web/OrchardVNext.Web.xproj similarity index 73% rename from src/OrchardVNext.Web/OrchardVNext.Web.kproj rename to src/OrchardVNext.Web/OrchardVNext.Web.xproj index 223239b2823..fe15c31522c 100644 --- a/src/OrchardVNext.Web/OrchardVNext.Web.kproj +++ b/src/OrchardVNext.Web/OrchardVNext.Web.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + 07d91819-4c81-434f-8de4-51d2b5a4eda8 Web @@ -20,10 +20,5 @@ False - - - - - - + \ No newline at end of file diff --git a/src/OrchardVNext.Web/Startup.cs b/src/OrchardVNext.Web/Startup.cs index f1d217ac0df..b3071413d6b 100644 --- a/src/OrchardVNext.Web/Startup.cs +++ b/src/OrchardVNext.Web/Startup.cs @@ -1,10 +1,16 @@ -using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Builder; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; using OrchardVNext.Environment; namespace OrchardVNext.Web { public class Startup { - public void Configure(IApplicationBuilder app) { - var host = OrchardStarter.CreateHost(app); + public void ConfigureServices(IServiceCollection services) { + OrchardStarter.ConfigureHost(services); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { + var host = OrchardStarter.CreateHost(app, loggerFactory); host.Initialize(); } } diff --git a/src/OrchardVNext.Web/project.json b/src/OrchardVNext.Web/project.json index 63bd4f51896..db74950861b 100644 --- a/src/OrchardVNext.Web/project.json +++ b/src/OrchardVNext.Web/project.json @@ -3,28 +3,31 @@ "version": "2.0.0-*", "exclude": [ "wwwroot", + "App_Data", "Core", "Modules", "Themes" ], "packExclude": [ + "node_modules", + "bower_components", "**.kproj", "**.user", "**.vspscc" ], "dependencies": { - "Microsoft.AspNet.Server.IIS": "1.0.0-beta4-*", - "Microsoft.AspNet.Mvc": "6.0.0-beta4-*", - "Microsoft.AspNet.Hosting": "1.0.0-beta4-*", - "Microsoft.AspNet.Server.WebListener": "1.0.0-beta4-*", - "OrchardVNext": "" + "Microsoft.AspNet.Server.IIS": "1.0.0-beta5-*", + "Microsoft.AspNet.Mvc": "6.0.0-beta5-*", + "Microsoft.AspNet.Hosting": "1.0.0-beta5-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5-*", + "OrchardVNext": "" }, "commands": { "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://local.orchardvnext.test1.com;http://local.orchardvnext.test2.com" }, "compilationOptions": { "define": [ "TRACE" ], "warningsAsErrors": true }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "dnx451": { }, + "dnxcore50": { } } } \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/ContentExtensions.cs b/src/OrchardVNext/ContentManagement/ContentExtensions.cs new file mode 100644 index 00000000000..d90a3856fa3 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/ContentExtensions.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OrchardVNext.ContentManagement { + public static class ContentExtensions { + /* Aggregate item/part type casting extension methods */ + + public static bool Is(this IContent content) { + return content == null ? false : content.ContentItem.Has(typeof(T)); + } + public static T As(this IContent content) where T : IContent { + return content == null ? default(T) : (T)content.ContentItem.Get(typeof(T)); + } + + public static bool Has(this IContent content) { + return content == null ? false : content.ContentItem.Has(typeof(T)); + } + public static T Get(this IContent content) where T : IContent { + return content == null ? default(T) : (T)content.ContentItem.Get(typeof(T)); + } + + public static IEnumerable AsPart(this IEnumerable items) where T : IContent { + return items == null ? null : items.Where(item => item.Is()).Select(item => item.As()); + } + + public static bool IsPublished(this IContent content) { + return content.ContentItem.VersionRecord != null && content.ContentItem.VersionRecord.Published; + } + public static bool HasDraft(this IContent content) { + return ( + (content.ContentItem.VersionRecord != null) + && ((content.ContentItem.VersionRecord.Published == false) + || (content.ContentItem.VersionRecord.Published && content.ContentItem.VersionRecord.Latest == false))); + } + } +} diff --git a/src/OrchardVNext/ContentManagement/ContentField.cs b/src/OrchardVNext/ContentManagement/ContentField.cs new file mode 100644 index 00000000000..1079b658030 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/ContentField.cs @@ -0,0 +1,14 @@ +using OrchardVNext.ContentManagement.FieldStorage; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement { + public class ContentField { + public string Name { get { return PartFieldDefinition.Name; } } + public string DisplayName { get { return PartFieldDefinition.DisplayName; } } + + public ContentPartFieldDefinition PartFieldDefinition { get; set; } + public ContentFieldDefinition FieldDefinition { get { return PartFieldDefinition.FieldDefinition; } } + + public IFieldStorage Storage { get; set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/ContentItem.cs b/src/OrchardVNext/ContentManagement/ContentItem.cs new file mode 100644 index 00000000000..219cbe0c43c --- /dev/null +++ b/src/OrchardVNext/ContentManagement/ContentItem.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using OrchardVNext.ContentManagement.MetaData.Models; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement { + public class ContentItem : IContent { + public ContentItem() { + _parts = new List(); + } + + private readonly IList _parts; + + ContentItem IContent.ContentItem => this; + + public int Id { get { return Record?.Id ?? 0; } } + public int Version { get { return VersionRecord?.Number ?? 0; } } + + public string ContentType { get; set; } + public ContentTypeDefinition TypeDefinition { get; set; } + public ContentItemRecord Record { get { return VersionRecord?.ContentItemRecord; } } + public ContentItemVersionRecord VersionRecord { get; set; } + + public IEnumerable Parts => _parts; + + public IContentManager ContentManager { get; set; } + + public bool Has(Type partType) { + return partType == typeof(ContentItem) || _parts.Any(partType.IsInstanceOfType); + } + + public IContent Get(Type partType) { + if (partType == typeof(ContentItem)) + return this; + return _parts.FirstOrDefault(partType.IsInstanceOfType); + } + + public void Weld(ContentPart part) { + part.ContentItem = this; + _parts.Add(part); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/ContentPart.cs b/src/OrchardVNext/ContentManagement/ContentPart.cs new file mode 100644 index 00000000000..7832f877313 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/ContentPart.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.Mvc; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement { + public class ContentPart : IContent { + private readonly IList _fields; + + public ContentPart() { + _fields = new List(); + } + + public virtual ContentItem ContentItem { get; set; } + + /// + /// The ContentItem's identifier. + /// + [HiddenInput(DisplayValue = false)] + public int Id => ContentItem.Id; + + public ContentTypeDefinition TypeDefinition => ContentItem.TypeDefinition; + public ContentTypePartDefinition TypePartDefinition { get; set; } + public ContentPartDefinition PartDefinition => TypePartDefinition.PartDefinition; + public SettingsDictionary Settings => TypePartDefinition.Settings; + + public IEnumerable Fields => _fields; + + + public bool Has(Type fieldType, string fieldName) { + return _fields.Any(field => field.Name == fieldName && fieldType.IsInstanceOfType(field)); + } + + public ContentField Get(Type fieldType, string fieldName) { + return _fields.FirstOrDefault(field => field.Name == fieldName && fieldType.IsInstanceOfType(field)); + } + + public void Weld(ContentField field) { + _fields.Add(field); + } + + public T Retrieve(string fieldName) { + return InfosetHelper.Retrieve(this, fieldName); + } + + public T RetrieveVersioned(string fieldName) { + return this.Retrieve(fieldName, true); + } + + public virtual void Store(string fieldName, T value) { + InfosetHelper.Store(this, fieldName, value); + } + + public virtual void StoreVersioned(string fieldName, T value) { + this.Store(fieldName, value, true); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/DefaultContentManager.cs b/src/OrchardVNext/ContentManagement/DefaultContentManager.cs new file mode 100644 index 00000000000..9e14271fcde --- /dev/null +++ b/src/OrchardVNext/ContentManagement/DefaultContentManager.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.ContentManagement.Handlers; +using OrchardVNext.ContentManagement.MetaData; +using OrchardVNext.ContentManagement.MetaData.Builders; +using OrchardVNext.ContentManagement.MetaData.Models; +using OrchardVNext.ContentManagement.Records; +using OrchardVNext.Data; +using OrchardVNext.Environment.Configuration; + +namespace OrchardVNext.ContentManagement { + public class DefaultContentManager : IContentManager { + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IContentManagerSession _contentManagerSession; + private readonly IEnumerable _handlers; + private readonly IContentItemStore _contentItemStore; + private readonly IContentStorageProvider _contentStorageProvider; + private readonly ShellSettings _shellSettings; + + private const string Published = "Published"; + private const string Draft = "Draft"; + + public DefaultContentManager( + IContentDefinitionManager contentDefinitionManager, + IContentManagerSession contentManagerSession, + IEnumerable handlers, + IContentItemStore contentItemStore, + IContentStorageProvider contentStorageProvider, + ShellSettings shellSettings) { + _contentDefinitionManager = contentDefinitionManager; + _contentManagerSession = contentManagerSession; + _shellSettings = shellSettings; + _handlers = handlers; + _contentItemStore = contentItemStore; + _contentStorageProvider = contentStorageProvider; + } + + public IEnumerable Handlers => _handlers; + + public IEnumerable GetContentTypeDefinitions() { + return _contentDefinitionManager.ListTypeDefinitions(); + } + + public virtual ContentItem New(string contentType) { + var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(contentType); + if (contentTypeDefinition == null) { + contentTypeDefinition = new ContentTypeDefinitionBuilder().Named(contentType).Build(); + } + + // create a new kernel for the model instance + var context = new ActivatingContentContext { + ContentType = contentTypeDefinition.Name, + Definition = contentTypeDefinition, + Builder = new ContentItemBuilder(contentTypeDefinition) + }; + + // invoke handlers to weld aspects onto kernel + Handlers.Invoke(handler => handler.Activating(context)); + + var context2 = new ActivatedContentContext { + ContentType = contentType, + ContentItem = context.Builder.Build() + }; + + // back-reference for convenience (e.g. getting metadata when in a view) + context2.ContentItem.ContentManager = this; + + Handlers.Invoke(handler => handler.Activated(context2)); + + var context3 = new InitializingContentContext { + ContentType = context2.ContentType, + ContentItem = context2.ContentItem, + }; + + Handlers.Invoke(handler => handler.Initializing(context3)); + Handlers.Invoke(handler => handler.Initialized(context3)); + + // composite result is returned + return context3.ContentItem; + } + + public virtual ContentItem Get(int id) { + return Get(id, VersionOptions.Published); + } + + public virtual ContentItem Get(int id, VersionOptions options) { + ContentItem contentItem; + + ContentItemVersionRecord versionRecord = null; + + // obtain the root records based on version options + if (options.VersionRecordId != 0) { + // short-circuit if item held in session + if (_contentManagerSession.RecallVersionRecordId(options.VersionRecordId, out contentItem)) { + return contentItem; + } + + versionRecord = _contentItemStore.Get(id, options).VersionRecord; + } + else if (options.VersionNumber != 0) { + // short-circuit if item held in session + if (_contentManagerSession.RecallVersionNumber(id, options.VersionNumber, out contentItem)) { + return contentItem; + } + + versionRecord = _contentItemStore.Get(id, options).VersionRecord; + } + else if (_contentManagerSession.RecallContentRecordId(id, out contentItem)) { + // try to reload a previously loaded published content item + + if (options.IsPublished) { + return contentItem; + } + + versionRecord = contentItem.VersionRecord; + } + + // no record means content item is not in db + if (versionRecord == null) { + // check in memory + var record = _contentItemStore.Get(id, options).VersionRecord; + if (record == null) { + return null; + } + + versionRecord = record; + } + + // return item if obtained earlier in session + if (_contentManagerSession.RecallVersionRecordId(versionRecord.Id, out contentItem)) { + if (options.IsDraftRequired && versionRecord.Published) { + return BuildNewVersion(contentItem); + } + return contentItem; + } + + // allocate instance and set record property + contentItem = New(versionRecord.ContentItemRecord.ContentType.Name); + contentItem.VersionRecord = versionRecord; + + // store in session prior to loading to avoid some problems with simple circular dependencies + _contentManagerSession.Store(contentItem); + + // create a context with a new instance to load + var context = new LoadContentContext(contentItem); + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + Handlers.Invoke(handler => handler.Loading(context)); + Handlers.Invoke(handler => handler.Loaded(context)); + + // when draft is required and latest is published a new version is appended + if (options.IsDraftRequired && versionRecord.Published) { + contentItem = BuildNewVersion(context.ContentItem); + } + + return contentItem; + } + + // public virtual IEnumerable GetAllVersions(int id) { + // return _contentItemVersionRepository + // .Fetch(x => x.ContentItemRecord.Id == id) + // .OrderBy(x => x.Number) + // .Select(x => Get(x.Id, VersionOptions.VersionRecord(x.Id))); + // } + + // public IEnumerable GetMany(IEnumerable ids, VersionOptions options) where T : class, IContent { + // var contentItemVersionRecords = GetManyImplementation(hints, (contentItemCriteria, contentItemVersionCriteria) => { + // contentItemCriteria.Add(Restrictions.In("Id", ids.ToArray())); + // if (options.IsPublished) { + // contentItemVersionCriteria.Add(Restrictions.Eq("Published", true)); + // } + // else if (options.IsLatest) { + // contentItemVersionCriteria.Add(Restrictions.Eq("Latest", true)); + // } + // else if (options.IsDraft && !options.IsDraftRequired) { + // contentItemVersionCriteria.Add( + // Restrictions.And(Restrictions.Eq("Published", false), + // Restrictions.Eq("Latest", true))); + // } + // else if (options.IsDraft || options.IsDraftRequired) { + // contentItemVersionCriteria.Add(Restrictions.Eq("Latest", true)); + // } + // }); + + // var itemsById = contentItemVersionRecords + // .Select(r => Get(r.ContentItemRecord.Id, options.IsDraftRequired ? options : VersionOptions.VersionRecord(r.Id))) + // .GroupBy(ci => ci.Id) + // .ToDictionary(g => g.Key); + + // return ids.SelectMany(id => { + // IGrouping values; + // return itemsById.TryGetValue(id, out values) ? values : Enumerable.Empty(); + // }).AsPart().ToArray(); + // } + + + // public IEnumerable GetManyByVersionId(IEnumerable versionRecordIds) { + // var contentItemVersionRecords = GetManyImplementation((contentItemCriteria, contentItemVersionCriteria) => + // contentItemVersionCriteria.Add(Restrictions.In("Id", versionRecordIds.ToArray()))); + + // var itemsById = contentItemVersionRecords + // .Select(r => Get(r.ContentItemRecord.Id, VersionOptions.VersionRecord(r.Id))) + // .GroupBy(ci => ci.VersionRecord.Id) + // .ToDictionary(g => g.Key); + + // return versionRecordIds.SelectMany(id => { + // IGrouping values; + // return itemsById.TryGetValue(id, out values) ? values : Enumerable.Empty(); + // }).ToArray(); + // } + + // public IEnumerable GetManyByVersionId(IEnumerable versionRecordIds) where T : class, IContent { + // return GetManyByVersionId(versionRecordIds).AsPart(); + // } + + // private IEnumerable GetManyImplementation(Action predicate) { + // var session = _sessionLocator.Value.For(typeof (ContentItemRecord)); + // var contentItemVersionCriteria = session.CreateCriteria(typeof (ContentItemVersionRecord)); + // var contentItemCriteria = contentItemVersionCriteria.CreateCriteria("ContentItemRecord"); + // predicate(contentItemCriteria, contentItemVersionCriteria); + + // var contentItemMetadata = session.SessionFactory.GetClassMetadata(typeof (ContentItemRecord)); + // var contentItemVersionMetadata = session.SessionFactory.GetClassMetadata(typeof (ContentItemVersionRecord)); + + // if (hints != QueryHints.Empty) { + // // break apart and group hints by their first segment + // var hintDictionary = hints.Records + // .Select(hint => new { Hint = hint, Segments = hint.Split('.') }) + // .GroupBy(item => item.Segments.FirstOrDefault()) + // .ToDictionary(grouping => grouping.Key, StringComparer.InvariantCultureIgnoreCase); + + // // locate hints that match properties in the ContentItemVersionRecord + // foreach (var hit in contentItemVersionMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + // contentItemVersionCriteria.SetFetchMode(hit.Hint, FetchMode.Eager); + // hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemVersionCriteria, ExtendCriteria); + // } + + // // locate hints that match properties in the ContentItemRecord + // foreach (var hit in contentItemMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + // contentItemVersionCriteria.SetFetchMode("ContentItemRecord." + hit.Hint, FetchMode.Eager); + // hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemCriteria, ExtendCriteria); + // } + + // if (hintDictionary.SelectMany(x => x.Value).Any(x => x.Segments.Count() > 1)) + // contentItemVersionCriteria.SetResultTransformer(new DistinctRootEntityResultTransformer()); + // } + + // contentItemCriteria.SetCacheable(true); + + // return contentItemVersionCriteria.List(); + // } + + // private static ICriteria ExtendCriteria(ICriteria criteria, string segment) { + // return criteria.GetCriteriaByPath(segment) ?? criteria.CreateCriteria(segment, JoinType.LeftOuterJoin); + // } + + public virtual void Publish(ContentItem contentItem) { + if (contentItem.VersionRecord.Published) { + return; + } + // create a context for the item and it's previous published record + var previous = contentItem.Record.Versions.SingleOrDefault(x => x.Published); + var context = new PublishContentContext(contentItem, previous); + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + Handlers.Invoke(handler => handler.Publishing(context)); + + if (context.Cancel) { + return; + } + + if (previous != null) { + previous.Published = false; + } + contentItem.VersionRecord.Published = true; + + Handlers.Invoke(handler => handler.Published(context)); + } + + public virtual void Unpublish(ContentItem contentItem) { + ContentItem publishedItem; + if (contentItem.VersionRecord.Published) { + // the version passed in is the published one + publishedItem = contentItem; + } + else { + // try to locate the published version of this item + publishedItem = Get(contentItem.Id, VersionOptions.Published); + } + + if (publishedItem == null) { + // no published version exists. no work to perform. + return; + } + + // create a context for the item. the publishing version is null in this case + // and the previous version is the one active prior to unpublishing. handlers + // should take this null check into account + var context = new PublishContentContext(contentItem, publishedItem.VersionRecord) { + PublishingItemVersionRecord = null + }; + + Handlers.Invoke(handler => handler.Unpublishing(context)); + + publishedItem.VersionRecord.Published = false; + + Handlers.Invoke(handler => handler.Unpublished(context)); + } + + // public virtual void Remove(ContentItem contentItem) { + // var activeVersions = _contentItemVersionRepository.Fetch(x => x.ContentItemRecord == contentItem.Record && (x.Published || x.Latest)); + // var context = new RemoveContentContext(contentItem); + + // Handlers.Invoke(handler => handler.Removing(context), Logger); + + // foreach (var version in activeVersions) { + // if (version.Published) { + // version.Published = false; + // } + // if (version.Latest) { + // version.Latest = false; + // } + // } + + // Handlers.Invoke(handler => handler.Removed(context), Logger); + // } + + // public virtual void Destroy(ContentItem contentItem) { + // var session = _sessionLocator.Value.For(typeof(ContentItemVersionRecord)); + // var context = new DestroyContentContext(contentItem); + + // // Give storage filters a chance to delete content part records. + // Handlers.Invoke(handler => handler.Destroying(context), Logger); + + // // Delete content item version and content item records. + // session + // .CreateQuery("delete from Orchard.ContentManagement.Records.ContentItemVersionRecord civ where civ.ContentItemRecord.Id = (:id)") + // .SetParameter("id", contentItem.Id) + // .ExecuteUpdate(); + + // // Delete the content item record itself. + // session + // .CreateQuery("delete from Orchard.ContentManagement.Records.ContentItemRecord ci where ci.Id = (:id)") + // .SetParameter("id", contentItem.Id) + // .ExecuteUpdate(); + + // Handlers.Invoke(handler => handler.Destroyed(context), Logger); + // } + + protected virtual ContentItem BuildNewVersion(ContentItem existingContentItem) { + var contentItemRecord = existingContentItem.Record; + + // locate the existing and the current latest versions, allocate building version + var existingItemVersionRecord = existingContentItem.VersionRecord; + var buildingItemVersionRecord = new ContentItemVersionRecord { + ContentItemRecord = contentItemRecord, + Latest = true, + Published = false, + Data = existingItemVersionRecord.Data, + }; + + + var latestVersion = contentItemRecord.Versions.SingleOrDefault(x => x.Latest); + + if (latestVersion != null) { + latestVersion.Latest = false; + buildingItemVersionRecord.Number = latestVersion.Number + 1; + } + else { + buildingItemVersionRecord.Number = contentItemRecord.Versions.Max(x => x.Number) + 1; + } + + contentItemRecord.Versions.Add(buildingItemVersionRecord); + _contentStorageProvider.Store(buildingItemVersionRecord); + + var buildingContentItem = New(existingContentItem.ContentType); + buildingContentItem.VersionRecord = buildingItemVersionRecord; + + var context = new VersionContentContext { + Id = existingContentItem.Id, + ContentType = existingContentItem.ContentType, + ContentItemRecord = contentItemRecord, + ExistingContentItem = existingContentItem, + BuildingContentItem = buildingContentItem, + ExistingItemVersionRecord = existingItemVersionRecord, + BuildingItemVersionRecord = buildingItemVersionRecord, + }; + Handlers.Invoke(handler => handler.Versioning(context)); + Handlers.Invoke(handler => handler.Versioned(context)); + + return context.BuildingContentItem; + } + + public virtual void Create(ContentItem contentItem) { + Create(contentItem, VersionOptions.Published); + } + + public virtual void Create(ContentItem contentItem, VersionOptions options) { + if (contentItem.VersionRecord == null) { + // produce root record to determine the model id + contentItem.VersionRecord = new ContentItemVersionRecord { + ContentItemRecord = new ContentItemRecord(), + Number = 1, + Latest = true, + Published = true + }; + } + + // add to the collection manually for the created case + contentItem.VersionRecord.ContentItemRecord.Versions.Add(contentItem.VersionRecord); + + // version may be specified + if (options.VersionNumber != 0) { + contentItem.VersionRecord.Number = options.VersionNumber; + } + + // draft flag on create is required for explicitly-published content items + if (options.IsDraft) { + contentItem.VersionRecord.Published = false; + } + + _contentItemStore.Store(contentItem); + + // build a context with the initialized instance to create + var context = new CreateContentContext(contentItem); + + // invoke handlers to add information to persistent stores + Handlers.Invoke(handler => handler.Creating(context)); + + // deferring the assignment of ContentType as loading a Record might force NHibernate to AutoFlush + // the ContentPart, and needs the ContentItemRecord to be created before (created in previous statement) + contentItem.VersionRecord.ContentItemRecord.ContentType = AcquireContentTypeRecord(contentItem.ContentType); + + Handlers.Invoke(handler => handler.Created(context)); + + + if (options.IsPublished) { + var publishContext = new PublishContentContext(contentItem, null); + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + Handlers.Invoke(handler => handler.Publishing(publishContext)); + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + Handlers.Invoke(handler => handler.Published(publishContext)); + } + } + + // public virtual ContentItem Clone(ContentItem contentItem) { + // // Mostly taken from: http://orchard.codeplex.com/discussions/396664 + // var importContentSession = new ImportContentSession(this); + + // var element = Export(contentItem); + + // // If a handler prevents this element from being exported, it can't be cloned + // if (element == null) { + // throw new InvalidOperationException("The content item couldn't be cloned because a handler prevented it from being exported."); + // } + + // var elementId = element.Attribute("Id"); + // var copyId = elementId.Value + "-copy"; + // elementId.SetValue(copyId); + // var status = element.Attribute("Status"); + // if (status != null) status.SetValue("Draft"); // So the copy is always a draft. + + // importContentSession.Set(copyId, element.Name.LocalName); + + // Import(element, importContentSession); + + // return importContentSession.Get(copyId, element.Name.LocalName); + // } + + // public virtual ContentItem Restore(ContentItem contentItem, VersionOptions options) { + // // Invoke handlers. + // Handlers.Invoke(handler => handler.Restoring(new RestoreContentContext(contentItem, options)), Logger); + + // // Get the latest version. + // var versions = contentItem.Record.Versions.OrderBy(x => x.Number).ToArray(); + // var latestVersionRecord = versions.SingleOrDefault(x => x.Latest) ?? versions.Last(); + + // // Get the specified version. + // var specifiedVersionContentItem = + // contentItem.VersionRecord.Number == options.VersionNumber || contentItem.VersionRecord.Id == options.VersionRecordId + // ? contentItem + // : Get(contentItem.Id, options); + + // // Create a new version record based on the specified version record. + // var rolledBackContentItem = BuildNewVersion(specifiedVersionContentItem); + // rolledBackContentItem.VersionRecord.Published = options.IsPublished; + + // // Invoke handlers. + // Handlers.Invoke(handler => handler.Restored(new RestoreContentContext(rolledBackContentItem, options)), Logger); + + // if (options.IsPublished) { + // // Unpublish the latest version. + // latestVersionRecord.Published = false; + + // var publishContext = new PublishContentContext(rolledBackContentItem, previousItemVersionRecord: latestVersionRecord); + + // Handlers.Invoke(handler => handler.Publishing(publishContext), Logger); + // Handlers.Invoke(handler => handler.Published(publishContext), Logger); + // } + + // return rolledBackContentItem; + // } + + // /// + // /// Lookup for a content item based on a . If multiple + // /// resolvers can give a result, the one with the highest priority is used. As soon as + // /// only one content item is returned from resolvers, it is returned as the result. + // /// + // /// The instance to lookup + // /// The instance represented by the identity object. + // public ContentItem ResolveIdentity(ContentIdentity contentIdentity) { + // var resolvers = _identityResolverSelectors.Value + // .Select(x => x.GetResolver(contentIdentity)) + // .Where(x => x != null) + // .OrderByDescending(x => x.Priority); + + // if (!resolvers.Any()) + // return null; + + // IEnumerable contentItems = null; + // foreach (var resolver in resolvers) { + // var resolved = resolver.Resolve(contentIdentity).ToArray(); + + // // first pass + // if (contentItems == null) { + // contentItems = resolved; + // } + // else { // subsquent passes means we need to intersect + // contentItems = contentItems.Intersect(resolved).ToArray(); + // } + + // if (contentItems.Count() == 1) { + // return contentItems.First(); + // } + // } + + // return contentItems.FirstOrDefault(); + // } + + // public ContentItemMetadata GetItemMetadata(IContent content) { + // var context = new GetContentItemMetadataContext { + // ContentItem = content.ContentItem, + // Metadata = new ContentItemMetadata() + // }; + + // Handlers.Invoke(handler => handler.GetContentItemMetadata(context), Logger); + + // return context.Metadata; + // } + + // public IEnumerable GetEditorGroupInfos(IContent content) { + // var metadata = GetItemMetadata(content); + // return metadata.EditorGroupInfo + // .GroupBy(groupInfo => groupInfo.Id) + // .Select(grouping => grouping.OrderBy(groupInfo => groupInfo.Position, new FlatPositionComparer()).FirstOrDefault()); + // } + + // public IEnumerable GetDisplayGroupInfos(IContent content) { + // var metadata = GetItemMetadata(content); + // return metadata.DisplayGroupInfo + // .GroupBy(groupInfo => groupInfo.Id) + // .Select(grouping => grouping.OrderBy(groupInfo => groupInfo.Position, new FlatPositionComparer()).FirstOrDefault()); + // } + + // public GroupInfo GetEditorGroupInfo(IContent content, string groupInfoId) { + // return GetEditorGroupInfos(content).FirstOrDefault(gi => string.Equals(gi.Id, groupInfoId, StringComparison.OrdinalIgnoreCase)); + // } + + // public GroupInfo GetDisplayGroupInfo(IContent content, string groupInfoId) { + // return GetDisplayGroupInfos(content).FirstOrDefault(gi => string.Equals(gi.Id, groupInfoId, StringComparison.OrdinalIgnoreCase)); + // } + + // public dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") { + // return _contentDisplay.Value.BuildDisplay(content, displayType, groupId); + // } + + // public dynamic BuildEditor(IContent content, string groupId = "") { + // return _contentDisplay.Value.BuildEditor(content, groupId); + // } + + // public dynamic UpdateEditor(IContent content, IUpdateModel updater, string groupId = "") { + // var context = new UpdateContentContext(content.ContentItem); + + // Handlers.Invoke(handler => handler.Updating(context), Logger); + + // var result = _contentDisplay.Value.UpdateEditor(content, updater, groupId); + + // Handlers.Invoke(handler => handler.Updated(context), Logger); + + // return result; + // } + + // public void Clear() { + // } + + // public IContentQuery Query() { + // var query = _context.Resolve(TypedParameter.From(this)); + // return query.ForPart(); + // } + + // public IHqlQuery HqlQuery() { + // return new DefaultHqlQuery(this, _sessionLocator.Value.For(typeof(ContentItemVersionRecord)), _sqlStatementProviders.Value, _shellSettings); + // } + + // // Insert or Update imported data into the content manager. + // // Call content item handlers. + // public void Import(XElement element, ImportContentSession importContentSession) { + // var elementId = element.Attribute("Id"); + // if (elementId == null) { + // return; + // } + + // var identity = elementId.Value; + + // if (String.IsNullOrWhiteSpace(identity)) { + // return; + // } + + // var status = element.Attribute("Status"); + + // var item = importContentSession.Get(identity, VersionOptions.Latest, XmlConvert.DecodeName(element.Name.LocalName)); + // if (item == null) { + // item = New(XmlConvert.DecodeName(element.Name.LocalName)); + // if (status != null && status.Value == "Draft") { + // Create(item, VersionOptions.Draft); + // } + // else { + // Create(item); + // } + // } + + // // create a version record if import handlers need it + // if(item.VersionRecord == null) { + // item.VersionRecord = new ContentItemVersionRecord { + // ContentItemRecord = new ContentItemRecord { + // ContentType = AcquireContentTypeRecord(item.ContentType) + // }, + // Number = 1, + // Latest = true, + // Published = true + // }; + // } + + // var context = new ImportContentContext(item, element, importContentSession); + // foreach (var contentHandler in Handlers) { + // contentHandler.Importing(context); + // } + + // foreach (var contentHandler in Handlers) { + // contentHandler.Imported(context); + // } + + // var savedItem = Get(item.Id, VersionOptions.Latest); + + // // the item has been pre-created in the first pass of the import, create it in db + // if(savedItem == null) { + // if (status != null && status.Value == "Draft") { + // Create(item, VersionOptions.Draft); + // } + // else { + // Create(item); + // } + // } + + // if (status == null || status.Value == Published) { + // Publish(item); + // } + // } + + // public XElement Export(ContentItem contentItem) { + // var context = new ExportContentContext(contentItem, new XElement(XmlConvert.EncodeLocalName(contentItem.ContentType))); + + // foreach (var contentHandler in Handlers) { + // contentHandler.Exporting(context); + // } + + // foreach (var contentHandler in Handlers) { + // contentHandler.Exported(context); + // } + + // if (context.Exclude) { + // return null; + // } + + // context.Data.SetAttributeValue("Id", GetItemMetadata(contentItem).Identity.ToString()); + // if (contentItem.IsPublished()) { + // context.Data.SetAttributeValue("Status", Published); + // } + // else { + // context.Data.SetAttributeValue("Status", Draft); + // } + + // return context.Data; + // } + + private ContentTypeRecord AcquireContentTypeRecord(string contentType) { + + var contentTypeRecord = _contentStorageProvider + .Query(x => x.Name == contentType) + .FirstOrDefault(); + + if (contentTypeRecord == null) { + //TEMP: this is not safe... ContentItem types could be created concurrently? + contentTypeRecord = new ContentTypeRecord { Name = contentType }; + _contentStorageProvider.Store(contentTypeRecord); + } + + var contentTypeId = contentTypeRecord.Id; + + // There is a case when a content type record is created locally but the transaction is actually + // cancelled. In this case we are caching an Id which is none existent, or might represent another + // content type. Thus we need to ensure that the cache is valid, or invalidate it and retrieve it + // another time. + + var result = _contentStorageProvider + .Query(x => x.Id == contentTypeId) + .FirstOrDefault(); + + if (result != null && result.Name.Equals(contentType, StringComparison.OrdinalIgnoreCase)) { + return result; + } + + // invalidate the cache entry and load it again + return AcquireContentTypeRecord(contentType); + } + + // public void Index(ContentItem contentItem, IDocumentIndex documentIndex) { + // var indexContentContext = new IndexContentContext(contentItem, documentIndex); + + // // dispatch to handlers to retrieve index information + // Handlers.Invoke(handler => handler.Indexing(indexContentContext), Logger); + + // Handlers.Invoke(handler => handler.Indexed(indexContentContext), Logger); + // } + //} + + //internal class CallSiteCollection : ConcurrentDictionary>> { + // private readonly Func>> _valueFactory; + + // public CallSiteCollection(Func>> callSiteFactory) { + // _valueFactory = callSiteFactory; + // } + + // public CallSiteCollection(Func callSiteBinderFactory) { + // _valueFactory = key => CallSite>.Create(callSiteBinderFactory(key)); + // } + + // public object Invoke(object callee, string key) { + // var callSite = GetOrAdd(key, _valueFactory); + // return callSite.Target(callSite, callee); + // } + } +} diff --git a/src/OrchardVNext/ContentManagement/DefaultContentManagerSession.cs b/src/OrchardVNext/ContentManagement/DefaultContentManagerSession.cs new file mode 100644 index 00000000000..41cfe94cd4a --- /dev/null +++ b/src/OrchardVNext/ContentManagement/DefaultContentManagerSession.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace OrchardVNext.ContentManagement { + public class DefaultContentManagerSession : IContentManagerSession { + private readonly IDictionary _itemByVersionRecordId = new Dictionary(); + private readonly IDictionary, ContentItem> _itemByVersionNumber = new Dictionary, ContentItem>(); + private readonly IDictionary _publishedItemsByContentRecordId = new Dictionary(); + + public void Store(ContentItem item) { + _itemByVersionRecordId.Add(item.VersionRecord.Id, item); + _itemByVersionNumber.Add(Tuple.Create(item.Id, item.Version), item); + + // is it the Published version ? + if (item.VersionRecord.Latest && item.VersionRecord.Published) { + _publishedItemsByContentRecordId[item.Id] = item; + } + } + + public bool RecallVersionRecordId(int id, out ContentItem item) { + return _itemByVersionRecordId.TryGetValue(id, out item); + } + + public bool RecallVersionNumber(int id, int version, out ContentItem item) { + return _itemByVersionNumber.TryGetValue(Tuple.Create(id, version), out item); + } + + public bool RecallContentRecordId(int id, out ContentItem item) { + return _publishedItemsByContentRecordId.TryGetValue(id, out item); + } + + public void Clear() { + _itemByVersionRecordId.Clear(); + _itemByVersionNumber.Clear(); + _publishedItemsByContentRecordId.Clear(); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/FieldStorageProviderSelector.cs b/src/OrchardVNext/ContentManagement/FieldStorage/FieldStorageProviderSelector.cs new file mode 100644 index 00000000000..3c018a82ed8 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/FieldStorageProviderSelector.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.FieldStorage { + public class FieldStorageProviderSelector : IFieldStorageProviderSelector { + public const string Storage = "Storage"; + public const string DefaultProviderName = "Infoset"; + + private readonly IEnumerable _storageProviders; + + public FieldStorageProviderSelector(IEnumerable storageProviders) { + _storageProviders = storageProviders; + } + + public IFieldStorageProvider GetProvider(ContentPartFieldDefinition partFieldDefinition) { + IFieldStorageProvider provider = null; + + string storage; + if (partFieldDefinition.Settings.TryGetValue(Storage, out storage)) + provider = Locate(storage); + + return provider ?? Locate(DefaultProviderName); + } + + private IFieldStorageProvider Locate(string providerName) { + return _storageProviders.FirstOrDefault(provider => provider.ProviderName == providerName); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorage.cs b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorage.cs new file mode 100644 index 00000000000..bd13ba174c6 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorage.cs @@ -0,0 +1,14 @@ +namespace OrchardVNext.ContentManagement.FieldStorage { + public interface IFieldStorage { + T Get(string name); + void Set(string name, T value); + } + public static class FieldStorageExtension{ + public static T Get(this IFieldStorage storage) { + return storage.Get(null); + } + public static void Set(this IFieldStorage storage, T value) { + storage.Set(null, value); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProvider.cs b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProvider.cs new file mode 100644 index 00000000000..e9f0595b818 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProvider.cs @@ -0,0 +1,11 @@ +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.FieldStorage { + public interface IFieldStorageProvider : IDependency { + string ProviderName { get; } + + IFieldStorage BindStorage( + ContentPart contentPart, + ContentPartFieldDefinition partFieldDefinition); + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProviderSelector.cs b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProviderSelector.cs new file mode 100644 index 00000000000..7f49a5b021d --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/IFieldStorageProviderSelector.cs @@ -0,0 +1,7 @@ +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.FieldStorage { + public interface IFieldStorageProviderSelector : IDependency { + IFieldStorageProvider GetProvider(ContentPartFieldDefinition partFieldDefinition); + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/Infoset.cs b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/Infoset.cs new file mode 100644 index 00000000000..ef64d8e3356 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/Infoset.cs @@ -0,0 +1,26 @@ +using System.Xml.Linq; + +namespace OrchardVNext.ContentManagement.FieldStorage.InfosetStorage { + public class Infoset { + private XElement _element; + + private void SetElement(XElement value) { + _element = value; + } + + public XElement Element { + get { + return _element ?? (_element = new XElement("Data")); + } + } + + public string Data { + get { + return _element == null ? null : Element.ToString(SaveOptions.DisableFormatting); + } + set { + SetElement(string.IsNullOrEmpty(value) ? null : XElement.Parse(value, LoadOptions.PreserveWhitespace)); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetHandler.cs b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetHandler.cs new file mode 100644 index 00000000000..474bbba0d8f --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetHandler.cs @@ -0,0 +1,34 @@ +using OrchardVNext.ContentManagement.Handlers; + +namespace OrchardVNext.ContentManagement.FieldStorage.InfosetStorage { + public class InfosetHandler : ContentHandlerBase { + public override void Activating(ActivatingContentContext context) { + context.Builder.Weld(); + } + + public override void Creating(CreateContentContext context) { + var infosetPart = context.ContentItem.As(); + if (infosetPart != null) { + context.ContentItemRecord.Data = infosetPart.Infoset.Data; + context.ContentItemVersionRecord.Data = infosetPart.VersionInfoset.Data; + + infosetPart.Infoset = context.ContentItemRecord.Infoset; + infosetPart.VersionInfoset = context.ContentItemVersionRecord.Infoset; + } + } + public override void Loading(LoadContentContext context) { + var infosetPart = context.ContentItem.As(); + if (infosetPart != null) { + infosetPart.Infoset = context.ContentItemRecord.Infoset; + infosetPart.VersionInfoset = context.ContentItemVersionRecord.Infoset; + } + } + public override void Versioning(VersionContentContext context) { + var infosetPart = context.BuildingContentItem.As(); + if (infosetPart != null) { + infosetPart.Infoset = context.ContentItemRecord.Infoset; + infosetPart.VersionInfoset = context.BuildingItemVersionRecord.Infoset; + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetPart.cs b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetPart.cs new file mode 100644 index 00000000000..c917869c9d4 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetPart.cs @@ -0,0 +1,103 @@ +using System.Xml; +using System.Xml.Linq; +using System.Reflection; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.FieldStorage.InfosetStorage { + public class InfosetPart : ContentPart { + public InfosetPart() { + Document = new DocumentRecord(); + } + + private DocumentRecord Document { get; set; } + + public Infoset Infoset { + get { return Document.Infoset; } + set { Document.Infoset = value; } + } + public Infoset VersionInfoset { + get { return Document.VersionInfoset; } + set { Document.VersionInfoset = value; } + } + + public string Get(string fieldName) { + return Get(fieldName, null); + } + + public string Get(string fieldName, string valueName) { + return Get(typeof(TPart).Name, fieldName, valueName, typeof(TPart).GetTypeInfo().IsAssignableFrom(typeof(ContentItemVersionRecord).GetTypeInfo())); + } + + public string Get(string partName, string fieldName) { + return Get(partName, fieldName, null, false); + } + + public string GetVersioned(string partName, string fieldName) { + return Get(partName, fieldName, null, true); + } + + public string Get(string partName, string fieldName, string valueName, bool versionable = false) { + + var element = versionable ? VersionInfoset.Element : Infoset.Element; + + var partElement = element.Element(XmlConvert.EncodeName(partName)); + if (partElement == null) { + return null; + } + var fieldElement = partElement.Element(XmlConvert.EncodeName(fieldName)); + if (fieldElement == null) { + return null; + } + if (string.IsNullOrEmpty(valueName)) { + return fieldElement.Value; + } + var valueAttribute = fieldElement.Attribute(XmlConvert.EncodeName(valueName)); + if (valueAttribute == null) { + return null; + } + return valueAttribute.Value; + } + + public void Set(string fieldName, string valueName, string value) { + Set(fieldName, value); + } + + public void Set(string fieldName, string value) { + Set(typeof(TPart).Name, fieldName, null, value, typeof(TPart).GetTypeInfo().IsAssignableFrom(typeof(ContentItemVersionRecord).GetTypeInfo())); + } + + public void Set(string partName, string fieldName, string value) { + Set(partName, fieldName, null, value, false); + } + + public void SetVersioned(string partName, string fieldName, string value) { + Set(partName, fieldName, null, value, true); + } + + public void Set(string partName, string fieldName, string valueName, string value, bool versionable = false) { + + var element = versionable ? VersionInfoset.Element : Infoset.Element; + + var encodedPartName = XmlConvert.EncodeName(partName); + var partElement = element.Element(encodedPartName); + if (partElement == null) { + partElement = new XElement(encodedPartName); + Infoset.Element.Add(partElement); + } + + var encodedFieldName = XmlConvert.EncodeName(fieldName); + var fieldElement = partElement.Element(encodedFieldName); + if (fieldElement == null) { + fieldElement = new XElement(encodedFieldName); + partElement.Add(fieldElement); + } + + if (string.IsNullOrEmpty(valueName)) { + fieldElement.Value = value ?? ""; + } + else { + fieldElement.SetAttributeValue(XmlConvert.EncodeName(valueName), value); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetStorageProvider.cs b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetStorageProvider.cs new file mode 100644 index 00000000000..e8fb2654cb9 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/InfosetStorage/InfosetStorageProvider.cs @@ -0,0 +1,59 @@ +using System.Xml; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.FieldStorage.InfosetStorage { + public class InfosetStorageProvider : IFieldStorageProvider { + public string ProviderName { + get { return FieldStorageProviderSelector.DefaultProviderName; } + } + + public IFieldStorage BindStorage(ContentPart contentPart, ContentPartFieldDefinition partFieldDefinition) { + var partName = XmlConvert.EncodeLocalName(contentPart.PartDefinition.Name); + var fieldName = XmlConvert.EncodeLocalName(partFieldDefinition.Name); + var infosetPart = contentPart.As(); + + return new SimpleFieldStorage( + (name, valueType) => Get(infosetPart.ContentItem.VersionRecord == null ? infosetPart.Infoset.Element : infosetPart.VersionInfoset.Element, partName, fieldName, name), + (name, valueType, value) => Set(infosetPart.ContentItem.VersionRecord == null ? infosetPart.Infoset.Element : infosetPart.VersionInfoset.Element, partName, fieldName, name, value)); + } + + private static string Get(XElement element, string partName, string fieldName, string valueName) { + var partElement = element.Element(partName); + if (partElement == null) { + return null; + } + var fieldElement = partElement.Element(fieldName); + if (fieldElement == null) { + return null; + } + if (string.IsNullOrEmpty(valueName)) { + return fieldElement.Value; + } + var valueAttribute = fieldElement.Attribute(XmlConvert.EncodeLocalName(valueName)); + if (valueAttribute == null) { + return null; + } + return valueAttribute.Value; + } + + private void Set(XElement element, string partName, string fieldName, string valueName, string value) { + var partElement = element.Element(partName); + if (partElement == null) { + partElement = new XElement(partName); + element.Add(partElement); + } + var fieldElement = partElement.Element(fieldName); + if (fieldElement == null) { + fieldElement = new XElement(fieldName); + partElement.Add(fieldElement); + } + if (string.IsNullOrEmpty(valueName)) { + fieldElement.Value = value; + } + else { + fieldElement.SetAttributeValue(XmlConvert.EncodeLocalName(valueName), value); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/FieldStorage/SimpleFieldStorage.cs b/src/OrchardVNext/ContentManagement/FieldStorage/SimpleFieldStorage.cs new file mode 100644 index 00000000000..4df63175b9e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/FieldStorage/SimpleFieldStorage.cs @@ -0,0 +1,50 @@ +using System; +using System.Globalization; +using System.Xml; +using System.Reflection; + +namespace OrchardVNext.ContentManagement.FieldStorage { + public class SimpleFieldStorage : IFieldStorage { + public SimpleFieldStorage(Func getter, Action setter) { + Getter = getter; + Setter = setter; + } + + public Func Getter { get; set; } + public Action Setter { get; set; } + + public T Get(string name) { + var value = Getter(name, typeof(T)); + if(string.IsNullOrEmpty(value)) { + return default(T); + } + + var t = typeof (T); + + // the T is nullable, convert using underlying type + if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { + t = Nullable.GetUnderlyingType(t); + } + + // using a special case for DateTime as it would lose milliseconds otherwise + if (typeof(T) == typeof(DateTime)) { + var result = XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Utc); + return (T) (object)result; + } + + return (T)Convert.ChangeType(value, t, CultureInfo.InvariantCulture); + } + + public void Set(string name, T value) { + + // using a special case for DateTime as it would lose milliseconds otherwise + if (typeof(T) == typeof(DateTime)) { + var text = ((DateTime)(object)value).ToString("o", CultureInfo.InvariantCulture); + Setter(name, typeof(T), text); + } + else { + Setter(name, typeof (T), Convert.ToString(value, CultureInfo.InvariantCulture)); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/ActivatedContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/ActivatedContentContext.cs new file mode 100644 index 00000000000..f9ed27f69a8 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ActivatedContentContext.cs @@ -0,0 +1,6 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public class ActivatedContentContext { + public string ContentType { get; set; } + public ContentItem ContentItem { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/ActivatingContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/ActivatingContentContext.cs new file mode 100644 index 00000000000..191d2a40066 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ActivatingContentContext.cs @@ -0,0 +1,9 @@ +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.Handlers { + public class ActivatingContentContext { + public string ContentType { get; set; } + public ContentTypeDefinition Definition { get; set; } + public ContentItemBuilder Builder { get; set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/ActivatingFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/ActivatingFilter.cs new file mode 100644 index 00000000000..ac77c95e8f1 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ActivatingFilter.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; + +namespace OrchardVNext.ContentManagement.Handlers { + /// + /// Filter reponsible for adding a part to a content item when content items are activated + /// + public class ActivatingFilter : IContentActivatingFilter where TPart : ContentPart, new() { + private readonly Func _predicate; + + public ActivatingFilter(Func predicate) { + _predicate = predicate; + } + + public ActivatingFilter(params string[] contentTypes) + : this(contentType => contentTypes.Contains(contentType)) { + } + + public void Activating(ActivatingContentContext context) { + if (_predicate(context.ContentType)) + context.Builder.Weld(); + } + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/ContentContextBase.cs b/src/OrchardVNext/ContentManagement/Handlers/ContentContextBase.cs new file mode 100644 index 00000000000..53d79954dd1 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ContentContextBase.cs @@ -0,0 +1,17 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class ContentContextBase { + protected ContentContextBase (ContentItem contentItem) { + ContentItem = contentItem; + Id = contentItem.Id; + ContentType = contentItem.ContentType; + ContentItemRecord = contentItem.Record; + } + + public int Id { get; private set; } + public string ContentType { get; private set; } + public ContentItem ContentItem { get; private set; } + public ContentItemRecord ContentItemRecord { get; private set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/ContentHandler.cs b/src/OrchardVNext/ContentManagement/Handlers/ContentHandler.cs new file mode 100644 index 00000000000..3958c0ac9ba --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ContentHandler.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OrchardVNext.ContentManagement.Handlers { + public abstract class ContentHandler : IContentHandler { + protected ContentHandler() { + Filters = new List(); + } + + public List Filters { get; set; } + + protected void OnActivated(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnActivated = handler }); + } + + protected void OnInitializing(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnInitializing = handler }); + } + + protected void OnInitialized(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnInitialized = handler }); + } + + protected void OnCreating(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnCreating = handler }); + } + + protected void OnCreated(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnCreated = handler }); + } + + protected void OnLoading(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnLoading = handler }); + } + + protected void OnLoaded(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnLoaded = handler }); + } + + protected void OnUpdating(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnUpdating = handler }); + } + + protected void OnUpdated(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnUpdated = handler }); + } + + protected void OnVersioning(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnVersioning = handler }); + } + + protected void OnVersioned(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnVersioned = handler }); + } + + protected void OnPublishing(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnPublishing = handler }); + } + + protected void OnPublished(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnPublished = handler }); + } + + protected void OnUnpublishing(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnUnpublishing = handler }); + } + + protected void OnUnpublished(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnUnpublished = handler }); + } + + protected void OnRemoving(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnRemoving = handler }); + } + + protected void OnRemoved(Action handler) where TPart : class, IContent { + Filters.Add(new InlineStorageFilter { OnRemoved = handler }); + } + + //protected void OnDestroying(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineStorageFilter { OnDestroying = handler }); + //} + + //protected void OnDestroyed(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineStorageFilter { OnDestroyed = handler }); + //} + + //protected void OnIndexing(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineStorageFilter { OnIndexing = handler }); + //} + + //protected void OnIndexed(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineStorageFilter { OnIndexed = handler }); + //} + + //protected void OnGetContentItemMetadata(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineTemplateFilter { OnGetItemMetadata = handler }); + //} + //protected void OnGetDisplayShape(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineTemplateFilter { OnGetDisplayShape = handler }); + //} + + //protected void OnGetEditorShape(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineTemplateFilter { OnGetEditorShape = handler }); + //} + + //protected void OnUpdateEditorShape(Action handler) where TPart : class, IContent { + // Filters.Add(new InlineTemplateFilter { OnUpdateEditorShape = handler }); + //} + + class InlineStorageFilter : StorageFilterBase where TPart : class, IContent { + public Action OnActivated { get; set; } + public Action OnInitializing { get; set; } + public Action OnInitialized { get; set; } + public Action OnCreating { get; set; } + public Action OnCreated { get; set; } + public Action OnLoading { get; set; } + public Action OnLoaded { get; set; } + public Action OnUpdating { get; set; } + public Action OnUpdated { get; set; } + public Action OnVersioning { get; set; } + public Action OnVersioned { get; set; } + public Action OnPublishing { get; set; } + public Action OnPublished { get; set; } + public Action OnUnpublishing { get; set; } + public Action OnUnpublished { get; set; } + public Action OnRemoving { get; set; } + public Action OnRemoved { get; set; } + //public Action OnIndexing { get; set; } + //public Action OnIndexed { get; set; } + //public Action OnRestoring { get; set; } + //public Action OnRestored { get; set; } + //public Action OnDestroying { get; set; } + //public Action OnDestroyed { get; set; } + protected override void Activated(ActivatedContentContext context, TPart instance) { + if (OnActivated != null) OnActivated(context, instance); + } + protected override void Initializing(InitializingContentContext context, TPart instance) { + if (OnInitializing != null) OnInitializing(context, instance); + } + protected override void Initialized(InitializingContentContext context, TPart instance) { + if (OnInitialized != null) OnInitialized(context, instance); + } + protected override void Creating(CreateContentContext context, TPart instance) { + if (OnCreating != null) OnCreating(context, instance); + } + protected override void Created(CreateContentContext context, TPart instance) { + if (OnCreated != null) OnCreated(context, instance); + } + protected override void Loading(LoadContentContext context, TPart instance) { + if (OnLoading != null) OnLoading(context, instance); + } + protected override void Loaded(LoadContentContext context, TPart instance) { + if (OnLoaded != null) OnLoaded(context, instance); + } + protected override void Updating(UpdateContentContext context, TPart instance) { + if (OnUpdating != null) OnUpdating(context, instance); + } + protected override void Updated(UpdateContentContext context, TPart instance) { + if (OnUpdated != null) OnUpdated(context, instance); + } + protected override void Versioning(VersionContentContext context, TPart existing, TPart building) { + if (OnVersioning != null) OnVersioning(context, existing, building); + } + protected override void Versioned(VersionContentContext context, TPart existing, TPart building) { + if (OnVersioned != null) OnVersioned(context, existing, building); + } + protected override void Publishing(PublishContentContext context, TPart instance) { + if (OnPublishing != null) OnPublishing(context, instance); + } + protected override void Published(PublishContentContext context, TPart instance) { + if (OnPublished != null) OnPublished(context, instance); + } + protected override void Unpublishing(PublishContentContext context, TPart instance) { + if (OnUnpublishing != null) OnUnpublishing(context, instance); + } + protected override void Unpublished(PublishContentContext context, TPart instance) { + if (OnUnpublished != null) OnUnpublished(context, instance); + } + protected override void Removing(RemoveContentContext context, TPart instance) { + if (OnRemoving != null) OnRemoving(context, instance); + } + protected override void Removed(RemoveContentContext context, TPart instance) { + if (OnRemoved != null) OnRemoved(context, instance); + } + //protected override void Indexing(IndexContentContext context, TPart instance) { + // if ( OnIndexing != null ) + // OnIndexing(context, instance); + //} + //protected override void Indexed(IndexContentContext context, TPart instance) { + // if ( OnIndexed != null ) + // OnIndexed(context, instance); + //} + //protected override void Restoring(RestoreContentContext context, TPart instance) { + // if (OnRestoring != null) + // OnRestoring(context, instance); + //} + //protected override void Restored(RestoreContentContext context, TPart instance) { + // if (OnRestored != null) + // OnRestored(context, instance); + //} + //protected override void Destroying(DestroyContentContext context, TPart instance) { + // if (OnDestroying != null) + // OnDestroying(context, instance); + //} + //protected override void Destroyed(DestroyContentContext context, TPart instance) { + // if (OnDestroyed != null) + // OnDestroyed(context, instance); + //} + } + + void IContentHandler.Activating(ActivatingContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Activating(context); + Activating(context); + } + + void IContentHandler.Activated(ActivatedContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Activated(context); + Activated(context); + } + + void IContentHandler.Initializing(InitializingContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Initializing(context); + Initializing(context); + } + + void IContentHandler.Initialized(InitializingContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Initialized(context); + Initialized(context); + } + + void IContentHandler.Creating(CreateContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Creating(context); + Creating(context); + } + + void IContentHandler.Created(CreateContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Created(context); + Created(context); + } + + void IContentHandler.Loading(LoadContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Loading(context); + Loading(context); + } + + void IContentHandler.Loaded(LoadContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Loaded(context); + Loaded(context); + } + + void IContentHandler.Updating(UpdateContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Updating(context); + Updating(context); + } + + void IContentHandler.Updated(UpdateContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Updated(context); + Updated(context); + } + + void IContentHandler.Versioning(VersionContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Versioning(context); + Versioning(context); + } + + void IContentHandler.Versioned(VersionContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Versioned(context); + Versioned(context); + } + + void IContentHandler.Publishing(PublishContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Publishing(context); + Publishing(context); + } + + void IContentHandler.Published(PublishContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Published(context); + Published(context); + } + + void IContentHandler.Unpublishing(PublishContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Unpublishing(context); + Unpublishing(context); + } + + void IContentHandler.Unpublished(PublishContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Unpublished(context); + Unpublished(context); + } + + void IContentHandler.Removing(RemoveContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Removing(context); + Removing(context); + } + + void IContentHandler.Removed(RemoveContentContext context) { + foreach (var filter in Filters.OfType()) + filter.Removed(context); + Removed(context); + } + + //void IContentHandler.Indexing(IndexContentContext context) { + // foreach ( var filter in Filters.OfType() ) + // filter.Indexing(context); + // Indexing(context); + //} + + //void IContentHandler.Indexed(IndexContentContext context) { + // foreach ( var filter in Filters.OfType() ) + // filter.Indexed(context); + // Indexed(context); + //} + + //void IContentHandler.Importing(ImportContentContext context) { + // Importing(context); + //} + + //void IContentHandler.Imported(ImportContentContext context) { + // Imported(context); + //} + + //void IContentHandler.Exporting(ExportContentContext context) { + // Exporting(context); + //} + + //void IContentHandler.Exported(ExportContentContext context) { + // Exported(context); + //} + + //void IContentHandler.Restoring(RestoreContentContext context) { + // foreach (var filter in Filters.OfType()) + // filter.Restoring(context); + // Restoring(context); + //} + + //void IContentHandler.Restored(RestoreContentContext context) { + // foreach (var filter in Filters.OfType()) + // filter.Restored(context); + // Restored(context); + //} + + //void IContentHandler.Destroying(DestroyContentContext context) { + // foreach (var filter in Filters.OfType()) + // filter.Destroying(context); + // Destroying(context); + //} + + //void IContentHandler.Destroyed(DestroyContentContext context) { + // foreach (var filter in Filters.OfType()) + // filter.Destroyed(context); + // Destroyed(context); + //} + + //void IContentHandler.GetContentItemMetadata(GetContentItemMetadataContext context) { + // foreach (var filter in Filters.OfType()) + // filter.GetContentItemMetadata(context); + // GetItemMetadata(context); + //} + //void IContentHandler.BuildDisplay(BuildDisplayContext context) { + // foreach (var filter in Filters.OfType()) + // filter.BuildDisplayShape(context); + // BuildDisplayShape(context); + //} + //void IContentHandler.BuildEditor(BuildEditorContext context) { + // foreach (var filter in Filters.OfType()) + // filter.BuildEditorShape(context); + // BuildEditorShape(context); + //} + //void IContentHandler.UpdateEditor(UpdateEditorContext context) { + // foreach (var filter in Filters.OfType()) + // filter.UpdateEditorShape(context); + // UpdateEditorShape(context); + //} + + protected virtual void Activating(ActivatingContentContext context) { } + protected virtual void Activated(ActivatedContentContext context) { } + + protected virtual void Initializing(InitializingContentContext context) { } + protected virtual void Initialized(InitializingContentContext context) { } + + protected virtual void Creating(CreateContentContext context) { } + protected virtual void Created(CreateContentContext context) { } + + protected virtual void Loading(LoadContentContext context) { } + protected virtual void Loaded(LoadContentContext context) { } + + protected virtual void Updating(UpdateContentContext context) { } + protected virtual void Updated(UpdateContentContext context) { } + + protected virtual void Versioning(VersionContentContext context) { } + protected virtual void Versioned(VersionContentContext context) { } + + protected virtual void Publishing(PublishContentContext context) { } + protected virtual void Published(PublishContentContext context) { } + + protected virtual void Unpublishing(PublishContentContext context) { } + protected virtual void Unpublished(PublishContentContext context) { } + + protected virtual void Removing(RemoveContentContext context) { } + protected virtual void Removed(RemoveContentContext context) { } + + //protected virtual void Indexing(IndexContentContext context) { } + //protected virtual void Indexed(IndexContentContext context) { } + + //protected virtual void Importing(ImportContentContext context) { } + //protected virtual void Imported(ImportContentContext context) { } + //protected virtual void Exporting(ExportContentContext context) { } + //protected virtual void Exported(ExportContentContext context) { } + //protected virtual void Restoring(RestoreContentContext context) { } + //protected virtual void Restored(RestoreContentContext context) { } + //protected virtual void Destroying(DestroyContentContext context) { } + //protected virtual void Destroyed(DestroyContentContext context) { } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/ContentHandlerBase.cs b/src/OrchardVNext/ContentManagement/Handlers/ContentHandlerBase.cs new file mode 100644 index 00000000000..c3fbb16b434 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ContentHandlerBase.cs @@ -0,0 +1,24 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public class ContentHandlerBase : IContentHandler { + public virtual void Activating(ActivatingContentContext context) {} + public virtual void Activated(ActivatedContentContext context) {} + public virtual void Initializing(InitializingContentContext context) { } + public virtual void Initialized(InitializingContentContext context) { } + public virtual void Creating(CreateContentContext context) { } + public virtual void Created(CreateContentContext context) {} + public virtual void Loading(LoadContentContext context) {} + public virtual void Loaded(LoadContentContext context) { } + public virtual void Updating(UpdateContentContext context) { } + public virtual void Updated(UpdateContentContext context) { } + public virtual void Versioning(VersionContentContext context) { } + public virtual void Versioned(VersionContentContext context) { } + public virtual void Publishing(PublishContentContext context) { } + public virtual void Published(PublishContentContext context) { } + public virtual void Unpublishing(PublishContentContext context) { } + public virtual void Unpublished(PublishContentContext context) { } + public virtual void Removing(RemoveContentContext context) { } + public virtual void Removed(RemoveContentContext context) { } + //public virtual void Indexing(IndexContentContext context) { } + //public virtual void Indexed(IndexContentContext context) { } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/ContentItemBuilder.cs b/src/OrchardVNext/ContentManagement/Handlers/ContentItemBuilder.cs new file mode 100644 index 00000000000..16184a58a14 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/ContentItemBuilder.cs @@ -0,0 +1,66 @@ +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.Handlers { + /// + /// Builds a contentitem based on its the type definition (). + /// + public class ContentItemBuilder { + private readonly ContentTypeDefinition _definition; + private readonly ContentItem _item; + + /// + /// Constructs a new Content Item Builder instance. + /// + /// The definition for the content item to be built. + public ContentItemBuilder(ContentTypeDefinition definition) { + _definition = definition; + + // TODO: could / should be done on the build method ? + _item = new ContentItem { + ContentType = definition.Name, + TypeDefinition = definition + }; + } + + public ContentItem Build() { + return _item; + } + + /// + /// Welds a new part to the content item. If a part of the same type is already welded nothing is done. + /// + /// The type of the part to be welded. + /// A new Content Item Builder with the item having the new part welded. + public ContentItemBuilder Weld() where TPart : ContentPart, new() { + + // if the part hasn't be weld yet + if (_item.Parts.FirstOrDefault(part => part.GetType().Equals(typeof(TPart))) == null) { + var partName = typeof(TPart).Name; + + // obtain the type definition for the part + var typePartDefinition = _definition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName); + if (typePartDefinition == null) { + // If the content item's type definition does not define the part; use an empty type definition. + typePartDefinition = new ContentTypePartDefinition( + new ContentPartDefinition(partName), + new SettingsDictionary()); + } + + // build and weld the part + var part = new TPart { TypePartDefinition = typePartDefinition }; + _item.Weld(part); + } + + return this; + } + + /// + /// Welds a part to the content item. + /// + public ContentItemBuilder Weld(ContentPart contentPart) { + _item.Weld(contentPart); + return this; + } + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/CreateContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/CreateContentContext.cs new file mode 100644 index 00000000000..a73b18b7e02 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/CreateContentContext.cs @@ -0,0 +1,11 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class CreateContentContext : ContentContextBase { + public CreateContentContext(ContentItem contentItem) : base(contentItem) { + ContentItemVersionRecord = contentItem.VersionRecord; + } + + public ContentItemVersionRecord ContentItemVersionRecord { get; set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/IContentActivatingFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/IContentActivatingFilter.cs new file mode 100644 index 00000000000..b1703b5c9b0 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/IContentActivatingFilter.cs @@ -0,0 +1,5 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public interface IContentActivatingFilter : IContentFilter { + void Activating(ActivatingContentContext context); + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/IContentFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/IContentFilter.cs new file mode 100644 index 00000000000..a28b7adf1cc --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/IContentFilter.cs @@ -0,0 +1,4 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public interface IContentFilter { + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/IContentHandler.cs b/src/OrchardVNext/ContentManagement/Handlers/IContentHandler.cs new file mode 100644 index 00000000000..cff4f7e1f27 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/IContentHandler.cs @@ -0,0 +1,24 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public interface IContentHandler : IDependency { + void Activating(ActivatingContentContext context); + void Activated(ActivatedContentContext context); + void Initializing(InitializingContentContext context); + void Initialized(InitializingContentContext context); + void Creating(CreateContentContext context); + void Created(CreateContentContext context); + void Loading(LoadContentContext context); + void Loaded(LoadContentContext context); + void Updating(UpdateContentContext context); + void Updated(UpdateContentContext context); + void Versioning(VersionContentContext context); + void Versioned(VersionContentContext context); + void Publishing(PublishContentContext context); + void Published(PublishContentContext context); + void Unpublishing(PublishContentContext context); + void Unpublished(PublishContentContext context); + void Removing(RemoveContentContext context); + void Removed(RemoveContentContext context); + //void Indexing(IndexContentContext context); + //void Indexed(IndexContentContext context); + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/IContentStorageFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/IContentStorageFilter.cs new file mode 100644 index 00000000000..4da688f0765 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/IContentStorageFilter.cs @@ -0,0 +1,27 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public interface IContentStorageFilter : IContentFilter { + void Activated(ActivatedContentContext context); + void Initializing(InitializingContentContext context); + void Initialized(InitializingContentContext context); + void Creating(CreateContentContext context); + void Created(CreateContentContext context); + void Loading(LoadContentContext context); + void Loaded(LoadContentContext context); + void Updating(UpdateContentContext context); + void Updated(UpdateContentContext context); + void Versioning(VersionContentContext context); + void Versioned(VersionContentContext context); + void Publishing(PublishContentContext context); + void Published(PublishContentContext context); + void Unpublishing(PublishContentContext context); + void Unpublished(PublishContentContext context); + void Removing(RemoveContentContext context); + void Removed(RemoveContentContext context); + //void Indexing(IndexContentContext context); + //void Indexed(IndexContentContext context); + //void Restoring(RestoreContentContext context); + //void Restored(RestoreContentContext context); + //void Destroying(DestroyContentContext context); + //void Destroyed(DestroyContentContext context); + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/IndexContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/IndexContentContext.cs new file mode 100644 index 00000000000..513e8a5f99e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/IndexContentContext.cs @@ -0,0 +1,13 @@ +//using OrchardVNext.Indexing; + +//namespace OrchardVNext.ContentManagement.Handlers { +// public class IndexContentContext : ContentContextBase { + +// public IDocumentIndex DocumentIndex { get; private set; } + +// public IndexContentContext(ContentItem contentItem, IDocumentIndex documentIndex) +// : base(contentItem) { +// DocumentIndex = documentIndex; +// } +// } +//} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/InitializingContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/InitializingContentContext.cs new file mode 100644 index 00000000000..beb3bb7d463 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/InitializingContentContext.cs @@ -0,0 +1,6 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public class InitializingContentContext { + public string ContentType { get; set; } + public ContentItem ContentItem { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/LoadContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/LoadContentContext.cs new file mode 100644 index 00000000000..a4f98fd53da --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/LoadContentContext.cs @@ -0,0 +1,11 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class LoadContentContext : ContentContextBase { + public LoadContentContext(ContentItem contentItem) : base(contentItem) { + ContentItemVersionRecord = contentItem.VersionRecord; + } + + public ContentItemVersionRecord ContentItemVersionRecord { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/PublishContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/PublishContentContext.cs new file mode 100644 index 00000000000..21e07b8a2e1 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/PublishContentContext.cs @@ -0,0 +1,15 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class PublishContentContext : ContentContextBase { + public PublishContentContext(ContentItem contentItem, ContentItemVersionRecord previousItemVersionRecord) : base(contentItem) { + PublishingItemVersionRecord = contentItem.VersionRecord; + PreviousItemVersionRecord = previousItemVersionRecord; + } + + public ContentItemVersionRecord PublishingItemVersionRecord { get; set; } + public ContentItemVersionRecord PreviousItemVersionRecord { get; set; } + + public bool Cancel { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/RemoveContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/RemoveContentContext.cs new file mode 100644 index 00000000000..eead0816a51 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/RemoveContentContext.cs @@ -0,0 +1,6 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public class RemoveContentContext : ContentContextBase { + public RemoveContentContext(ContentItem contentItem) : base(contentItem) { + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/StorageFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/StorageFilter.cs new file mode 100644 index 00000000000..3b66830f840 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/StorageFilter.cs @@ -0,0 +1,65 @@ +//using System; +//using OrchardVNext.ContentManagement.Records; +//using OrchardVNext.Data; + +//namespace OrchardVNext.ContentManagement.Handlers { +// public static class StorageFilter { +// public static StorageFilter For(IRepository repository) where TRecord : ContentPartRecord, new() { +// if (typeof(TRecord).IsSubclassOf(typeof(ContentPartVersionRecord))) { +// var filterType = typeof(StorageVersionFilter<>).MakeGenericType(typeof(TRecord)); +// return (StorageFilter)Activator.CreateInstance(filterType, repository); +// } +// return new StorageFilter(repository); +// } +// } + +// public class StorageFilter : StorageFilterBase> where TRecord : ContentPartRecord, new() { +// protected readonly IRepository _repository; + +// public StorageFilter(IRepository repository) { +// if (this.GetType() == typeof(StorageFilter) && typeof(TRecord).IsSubclassOf(typeof(ContentPartVersionRecord))) { +// throw new ArgumentException( +// string.Format("Use {0} (or {1}.For()) for versionable record types", typeof(StorageVersionFilter<>).Name, typeof(StorageFilter).Name), +// "repository"); +// } + +// _repository = repository; +// } + +// protected virtual TRecord GetRecordCore(ContentItemVersionRecord versionRecord) { +// return _repository.Get(versionRecord.ContentItemRecord.Id); +// } + +// protected virtual TRecord CreateRecordCore(ContentItemVersionRecord versionRecord, TRecord record) { +// record.ContentItemRecord = versionRecord.ContentItemRecord; +// _repository.Create(record); +// return record; +// } + +// protected override void Activated(ActivatedContentContext context, ContentPart instance) { +// if (instance.Record != null) { +// throw new InvalidOperationException(string.Format( +// "Having more than one storage filter for a given part ({0}) is invalid.", +// typeof(ContentPart).FullName)); +// } +// instance.Record = new TRecord(); +// } + +// protected override void Creating(CreateContentContext context, ContentPart instance) { +// CreateRecordCore(context.ContentItemVersionRecord, instance.Record); +// } + +// protected override void Loading(LoadContentContext context, ContentPart instance) { +// var versionRecord = context.ContentItemVersionRecord; +// instance._record.Loader(prior => GetRecordCore(versionRecord) ?? CreateRecordCore(versionRecord, prior)); +// } + +// protected override void Versioning(VersionContentContext context, ContentPart existing, ContentPart building) { +// building.Record = existing.Record; +// } + +// protected override void Destroying(DestroyContentContext context, ContentPart instance) { +// _repository.Delete(instance.Record); +// } +// } +//} diff --git a/src/OrchardVNext/ContentManagement/Handlers/StorageFilterBase.cs b/src/OrchardVNext/ContentManagement/Handlers/StorageFilterBase.cs new file mode 100644 index 00000000000..3a4c13e2158 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/StorageFilterBase.cs @@ -0,0 +1,144 @@ +namespace OrchardVNext.ContentManagement.Handlers { + public abstract class StorageFilterBase : IContentStorageFilter where TPart : class, IContent { + + protected virtual void Activated(ActivatedContentContext context, TPart instance) { } + protected virtual void Activating(ActivatingContentContext context, TPart instance) { } + protected virtual void Initializing(InitializingContentContext context, TPart instance) { } + protected virtual void Initialized(InitializingContentContext context, TPart instance) { } + protected virtual void Creating(CreateContentContext context, TPart instance) { } + protected virtual void Created(CreateContentContext context, TPart instance) { } + protected virtual void Loading(LoadContentContext context, TPart instance) { } + protected virtual void Loaded(LoadContentContext context, TPart instance) { } + protected virtual void Updating(UpdateContentContext context, TPart instance) { } + protected virtual void Updated(UpdateContentContext context, TPart instance) { } + protected virtual void Versioning(VersionContentContext context, TPart existing, TPart building) { } + protected virtual void Versioned(VersionContentContext context, TPart existing, TPart building) { } + protected virtual void Publishing(PublishContentContext context, TPart instance) { } + protected virtual void Published(PublishContentContext context, TPart instance) { } + protected virtual void Unpublishing(PublishContentContext context, TPart instance) { } + protected virtual void Unpublished(PublishContentContext context, TPart instance) { } + protected virtual void Removing(RemoveContentContext context, TPart instance) { } + protected virtual void Removed(RemoveContentContext context, TPart instance) { } + //protected virtual void Indexing(IndexContentContext context, TPart instance) { } + //protected virtual void Indexed(IndexContentContext context, TPart instance) { } + //protected virtual void Restoring(RestoreContentContext context, TPart instance) { } + //protected virtual void Restored(RestoreContentContext context, TPart instance) { } + //protected virtual void Destroying(DestroyContentContext context, TPart instance) { } + //protected virtual void Destroyed(DestroyContentContext context, TPart instance) { } + + void IContentStorageFilter.Activated(ActivatedContentContext context) { + if (context.ContentItem.Is()) + Activated(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Initializing(InitializingContentContext context) { + if (context.ContentItem.Is()) + Initializing(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Initialized(InitializingContentContext context) { + if (context.ContentItem.Is()) + Initialized(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Creating(CreateContentContext context) { + if (context.ContentItem.Is()) + Creating(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Created(CreateContentContext context) { + if (context.ContentItem.Is()) + Created(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Loading(LoadContentContext context) { + if (context.ContentItem.Is()) + Loading(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Loaded(LoadContentContext context) { + if (context.ContentItem.Is()) + Loaded(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Updating(UpdateContentContext context) { + if (context.ContentItem.Is()) + Updating(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Updated(UpdateContentContext context) { + if (context.ContentItem.Is()) + Updated(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Versioning(VersionContentContext context) { + if (context.ExistingContentItem.Is() || context.BuildingContentItem.Is()) + Versioning(context, context.ExistingContentItem.As(), context.BuildingContentItem.As()); + } + + void IContentStorageFilter.Versioned(VersionContentContext context) { + if (context.ExistingContentItem.Is() || context.BuildingContentItem.Is()) + Versioned(context, context.ExistingContentItem.As(), context.BuildingContentItem.As()); + } + + void IContentStorageFilter.Publishing(PublishContentContext context) { + if (context.ContentItem.Is()) + Publishing(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Published(PublishContentContext context) { + if (context.ContentItem.Is()) + Published(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Unpublishing(PublishContentContext context) { + if (context.ContentItem.Is()) + Unpublishing(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Unpublished(PublishContentContext context) { + if (context.ContentItem.Is()) + Unpublished(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Removing(RemoveContentContext context) { + if (context.ContentItem.Is()) + Removing(context, context.ContentItem.As()); + } + + void IContentStorageFilter.Removed(RemoveContentContext context) { + if (context.ContentItem.Is()) + Removed(context, context.ContentItem.As()); + } + + //void IContentStorageFilter.Indexing(IndexContentContext context) { + // if ( context.ContentItem.Is() ) + // Indexing(context, context.ContentItem.As()); + //} + + //void IContentStorageFilter.Indexed(IndexContentContext context) { + // if ( context.ContentItem.Is() ) + // Indexed(context, context.ContentItem.As()); + //} + + //void IContentStorageFilter.Restoring(RestoreContentContext context) { + // if (context.ContentItem.Is()) + // Restoring(context, context.ContentItem.As()); + //} + + //void IContentStorageFilter.Restored(RestoreContentContext context) { + // if (context.ContentItem.Is()) + // Restored(context, context.ContentItem.As()); + //} + + //void IContentStorageFilter.Destroying(DestroyContentContext context) { + // if (context.ContentItem.Is()) + // Destroying(context, context.ContentItem.As()); + //} + + //void IContentStorageFilter.Destroyed(DestroyContentContext context) { + // if (context.ContentItem.Is()) + // Destroyed(context, context.ContentItem.As()); + //} + } +} diff --git a/src/OrchardVNext/ContentManagement/Handlers/StorageVersionFilter.cs b/src/OrchardVNext/ContentManagement/Handlers/StorageVersionFilter.cs new file mode 100644 index 00000000000..87b6d6c3a6e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/StorageVersionFilter.cs @@ -0,0 +1,47 @@ +//using System.Linq; +//using OrchardVNext.ContentManagement.Records; +//using OrchardVNext.Data; + +//namespace OrchardVNext.ContentManagement.Handlers { +// public class StorageVersionFilter : StorageFilter where TRecord : ContentPartVersionRecord, new() { +// public StorageVersionFilter(IRepository repository) +// : base(repository) { +// } + +// protected override TRecord GetRecordCore(ContentItemVersionRecord versionRecord) { +// return _repository.Get(versionRecord.Id); +// } + +// protected override TRecord CreateRecordCore(ContentItemVersionRecord versionRecord, TRecord record) { +// record.ContentItemRecord = versionRecord.ContentItemRecord; +// record.ContentItemVersionRecord = versionRecord; +// _repository.Create(record); +// return record; +// } + +// protected override void Versioning(VersionContentContext context, ContentPart existing, ContentPart building) { +// // move known ORM values over +// _repository.Copy(existing.Record, building.Record); + +// // only the up-reference to the particular version differs at this point +// building.Record.ContentItemVersionRecord = context.BuildingItemVersionRecord; + +// // push the new instance into the transaction and session +// _repository.Create(building.Record); +// } + +// protected override void Destroying(DestroyContentContext context, ContentPart instance) { +// // Get all content item version records. +// var allVersions = context.ContentItem.Record.Versions.ToArray(); + +// // For each version record, delete its part record (ID of versioned part records is the same as the ID of a version record). +// foreach (var versionRecord in allVersions) { +// var partRecord = _repository.Get(versionRecord.Id); + +// if (partRecord != null) +// _repository.Delete(partRecord); +// } +// } +// } + +//} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/UpdateContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/UpdateContentContext.cs new file mode 100644 index 00000000000..9806f912c6d --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/UpdateContentContext.cs @@ -0,0 +1,11 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class UpdateContentContext : ContentContextBase { + public UpdateContentContext(ContentItem contentItem) : base(contentItem) { + UpdatingItemVersionRecord = contentItem.VersionRecord; + } + + public ContentItemVersionRecord UpdatingItemVersionRecord { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Handlers/VersionContentContext.cs b/src/OrchardVNext/ContentManagement/Handlers/VersionContentContext.cs new file mode 100644 index 00000000000..f09526d99ce --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Handlers/VersionContentContext.cs @@ -0,0 +1,15 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.ContentManagement.Handlers { + public class VersionContentContext { + public int Id { get; set; } + public string ContentType { get; set; } + + public ContentItemRecord ContentItemRecord { get; set; } + public ContentItemVersionRecord ExistingItemVersionRecord { get; set; } + public ContentItemVersionRecord BuildingItemVersionRecord { get; set; } + + public ContentItem ExistingContentItem { get; set; } + public ContentItem BuildingContentItem { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/IContent.cs b/src/OrchardVNext/ContentManagement/IContent.cs new file mode 100644 index 00000000000..934693b4877 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/IContent.cs @@ -0,0 +1,10 @@ +namespace OrchardVNext.ContentManagement { + public interface IContent { + ContentItem ContentItem { get; } + + /// + /// The ContentItem's identifier. + /// + int Id { get; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/IContentManager.cs b/src/OrchardVNext/ContentManagement/IContentManager.cs new file mode 100644 index 00000000000..3e80ac75e74 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/IContentManager.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement { + /// + /// Content management functionality to deal with Orchard content items and their parts + /// + public interface IContentManager : IDependency { + IEnumerable GetContentTypeDefinitions(); + + /// + /// Instantiates a new content item with the specified type + /// + /// + /// The content item is not yet persisted! + /// + /// The name of the content type + ContentItem New(string contentType); + + + /// + /// Creates (persists) a new content item + /// + /// The content instance filled with all necessary data + void Create(ContentItem contentItem); + + /// + /// Creates (persists) a new content item with the specified version + /// + /// The content instance filled with all necessary data + /// The version to create the item with + void Create(ContentItem contentItem, VersionOptions options); + + + ///// + ///// Makes a clone of the content item + ///// + ///// The content item to clone + ///// Clone of the item + //ContentItem Clone(ContentItem contentItem); + + ///// + ///// Rolls back the specified content item by creating a new version based on the specified version. + ///// + ///// The content item to roll back. + ///// The version to roll back to. Either specify the version record id, version number, and IsPublished to publish the new version. + ///// Returns the latest version of the content item, which is based on the specified version. + //ContentItem Restore(ContentItem contentItem, VersionOptions options); + + /// + /// Gets the content item with the specified id + /// + /// Numeric id of the content item + ContentItem Get(int id); + + /// + /// Gets the content item with the specified id and version + /// + /// Numeric id of the content item + /// The version option + ContentItem Get(int id, VersionOptions options); + + ///// + ///// Gets all versions of the content item specified with its id + ///// + ///// Numeric id of the content item + //IEnumerable GetAllVersions(int id); + + //IEnumerable GetMany(IEnumerable ids, VersionOptions options) where T : class, IContent; + //IEnumerable GetManyByVersionId(IEnumerable versionRecordIds) where T : class, IContent; + //IEnumerable GetManyByVersionId(IEnumerable versionRecordIds); + + void Publish(ContentItem contentItem); + void Unpublish(ContentItem contentItem); + //void Remove(ContentItem contentItem); + + ///// + ///// Permanently deletes the specified content item, including all of its content part records. + ///// + //void Destroy(ContentItem contentItem); + } + + public class VersionOptions { + /// + /// Gets the latest version. + /// + public static VersionOptions Latest { get { return new VersionOptions { IsLatest = true }; } } + + /// + /// Gets the latest published version. + /// + public static VersionOptions Published { get { return new VersionOptions { IsPublished = true }; } } + + /// + /// Gets the latest draft version. + /// + public static VersionOptions Draft { get { return new VersionOptions { IsDraft = true }; } } + + /// + /// Gets the latest version and creates a new version draft based on it. + /// + public static VersionOptions DraftRequired { get { return new VersionOptions { IsDraft = true, IsDraftRequired = true }; } } + + /// + /// Gets all versions. + /// + public static VersionOptions AllVersions { get { return new VersionOptions { IsAllVersions = true }; } } + + /// + /// Gets a specific version based on its number. + /// + public static VersionOptions Number(int version) { return new VersionOptions { VersionNumber = version }; } + + /// + /// Gets a specific version based on the version record identifier. + /// + public static VersionOptions VersionRecord(int id) { return new VersionOptions { VersionRecordId = id }; } + + /// + /// Creates a new version based on the specified version number. + /// + public static VersionOptions Restore(int version, bool publish = false) { return new VersionOptions { VersionNumber = version, IsPublished = publish}; } + + public bool IsLatest { get; private set; } + public bool IsPublished { get; private set; } + public bool IsDraft { get; private set; } + public bool IsDraftRequired { get; private set; } + public bool IsAllVersions { get; private set; } + public int VersionNumber { get; private set; } + public int VersionRecordId { get; private set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/IContentManagerSession.cs b/src/OrchardVNext/ContentManagement/IContentManagerSession.cs new file mode 100644 index 00000000000..b51cd9823d0 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/IContentManagerSession.cs @@ -0,0 +1,11 @@ +namespace OrchardVNext.ContentManagement +{ + public interface IContentManagerSession : IDependency { + void Store(ContentItem item); + bool RecallVersionRecordId(int id, out ContentItem item); + bool RecallVersionNumber(int id, int version, out ContentItem item); + bool RecallContentRecordId(int id, out ContentItem item); + + void Clear(); + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/InfosetHelper.cs b/src/OrchardVNext/ContentManagement/InfosetHelper.cs new file mode 100644 index 00000000000..21baf000efd --- /dev/null +++ b/src/OrchardVNext/ContentManagement/InfosetHelper.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq.Expressions; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.FieldStorage.InfosetStorage; +using OrchardVNext.ContentManagement.Records; +using OrchardVNext.Utility; + +namespace OrchardVNext.ContentManagement { + public static class InfosetHelper { + + public static TProperty Retrieve(this TPart contentPart, + Expression> targetExpression, + TProperty defaultValue = default(TProperty), + bool versioned = false) where TPart : ContentPart { + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + var infosetPart = contentPart.As(); + var el = infosetPart == null + ? null + : (versioned ? infosetPart.VersionInfoset.Element : infosetPart.Infoset.Element) + .Element(contentPart.GetType().Name); + var attr = el != null ? el.Attribute(name) : default(XAttribute); + return attr == null ? defaultValue : XmlHelper.Parse(attr.Value); + } + + public static TProperty Retrieve(this ContentPart contentPart, string name, + bool versioned = false) { + var infosetPart = contentPart.As(); + var el = infosetPart == null + ? null + : (versioned ? infosetPart.VersionInfoset.Element : infosetPart.Infoset.Element) + .Element(contentPart.GetType().Name); + return el == null ? default(TProperty) : el.Attr(name); + } + + public static void Store(this TPart contentPart, + Expression> targetExpression, + TProperty value, bool versioned = false) where TPart : ContentPart { + + var partName = contentPart.GetType().Name; + var infosetPart = contentPart.As(); + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + Store(infosetPart, partName, name, value, versioned); + } + + public static void Store(this ContentPart contentPart, string name, + TProperty value, bool versioned = false) { + + var partName = contentPart.GetType().Name; + var infosetPart = contentPart.As(); + + Store(infosetPart, partName, name, value, versioned); + } + + public static void Store(this InfosetPart infosetPart, string partName, string name, TProperty value, bool versioned = false) { + + var infoset = (versioned ? infosetPart.VersionInfoset : infosetPart.Infoset); + var partElement = infoset.Element.Element(partName); + if (partElement == null) { + partElement = new XElement(partName); + infoset.Element.Add(partElement); + } + partElement.Attr(name, value); + } + + public static TProperty RetrieveValue(this TDocumentRecord documentRecord, + Expression> targetExpression, + TProperty defaultValue = default(TProperty), + bool versioned = false) where TDocumentRecord : DocumentRecord { + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + var el = documentRecord == null + ? null + : (versioned ? documentRecord.VersionInfoset.Element : documentRecord.Infoset.Element) + .Element(documentRecord.GetType().Name); + var attr = el != null ? el.Attribute(name) : default(XAttribute); + return attr == null ? defaultValue : XmlHelper.Parse(attr.Value); + } + + public static void StoreValue(this TDocumentRecord documentRecord, + Expression> targetExpression, + TProperty value, bool versioned = false) where TDocumentRecord : DocumentRecord { + + var partName = documentRecord.GetType().Name; + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + Store(documentRecord, partName, name, value, versioned); + } + + public static void Store(this DocumentRecord documentRecord, string name, + TProperty value, bool versioned = false) { + + var partName = documentRecord.GetType().Name; + + Store(documentRecord, partName, name, value, versioned); + } + + public static void Store(this DocumentRecord documentRecord, string partName, string name, TProperty value, bool versioned = false) { + + var infoset = (versioned ? documentRecord.VersionInfoset : documentRecord.Infoset); + var partElement = infoset.Element.Element(partName); + if (partElement == null) { + partElement = new XElement(partName); + infoset.Element.Add(partElement); + } + partElement.Attr(name, value); + } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs new file mode 100644 index 00000000000..19c216ae809 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Builders { + public class ContentPartDefinitionBuilder { + private readonly ContentPartDefinition _part; + private string _name; + private readonly IList _fields; + private readonly SettingsDictionary _settings; + + public ContentPartDefinition Current { get; private set; } + + public ContentPartDefinitionBuilder() + : this(new ContentPartDefinition(null)) { + } + + public ContentPartDefinitionBuilder(ContentPartDefinition existing) { + _part = existing; + + if (existing == null) { + _fields = new List(); + _settings = new SettingsDictionary(); + } + else { + _name = existing.Name; + _fields = existing.Fields.ToList(); + _settings = new SettingsDictionary(existing.Settings.ToDictionary(kv => kv.Key, kv => kv.Value)); + } + } + + public string Name { get { return _name; } } + + public ContentPartDefinition Build() { + return new ContentPartDefinition(_name, _fields, _settings); + } + + public ContentPartDefinitionBuilder Named(string name) { + _name = name; + return this; + } + + public ContentPartDefinitionBuilder RemoveField(string fieldName) { + var existingField = _fields.SingleOrDefault(x => x.Name == fieldName); + if (existingField != null) { + _fields.Remove(existingField); + } + return this; + } + + public ContentPartDefinitionBuilder WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public ContentPartDefinitionBuilder WithField(string fieldName) { + return WithField(fieldName, configuration => { }); + } + + public ContentPartDefinitionBuilder WithField(string fieldName, Action configuration) { + + var existingField = _fields.FirstOrDefault(x => x.Name == fieldName); + if (existingField != null) { + var toRemove = _fields.Where(x => x.Name == fieldName).ToArray(); + foreach (var remove in toRemove) { + _fields.Remove(remove); + } + } + else { + existingField = new ContentPartFieldDefinition(fieldName); + } + var configurer = new FieldConfigurerImpl(existingField, _part); + configuration(configurer); + _fields.Add(configurer.Build()); + return this; + } + + class FieldConfigurerImpl : ContentPartFieldDefinitionBuilder { + private ContentFieldDefinition _fieldDefinition; + private readonly ContentPartDefinition _partDefinition; + private readonly string _fieldName; + + public FieldConfigurerImpl(ContentPartFieldDefinition field, ContentPartDefinition part) + : base(field) { + _fieldDefinition = field.FieldDefinition; + _fieldName = field.Name; + _partDefinition = part; + } + + public override ContentPartFieldDefinition Build() { + return new ContentPartFieldDefinition(_fieldDefinition, _fieldName, _settings); + } + + public override string Name { + get { return _fieldName; } + } + + public override string FieldType { + get { return _fieldDefinition.Name; } + } + + public override string PartName { + get { return _partDefinition.Name; } + } + + public override ContentPartFieldDefinitionBuilder OfType(ContentFieldDefinition fieldDefinition) { + _fieldDefinition = fieldDefinition; + return this; + } + + public override ContentPartFieldDefinitionBuilder OfType(string fieldType) { + _fieldDefinition = new ContentFieldDefinition(fieldType); + return this; + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs new file mode 100644 index 00000000000..2a8acb1c49e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentPartFieldDefinitionBuilder.cs @@ -0,0 +1,35 @@ +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Builders { + public abstract class ContentPartFieldDefinitionBuilder { + protected readonly SettingsDictionary _settings; + + public ContentPartFieldDefinition Current { get; private set; } + + protected ContentPartFieldDefinitionBuilder(ContentPartFieldDefinition field) { + Current = field; + + _settings = new SettingsDictionary(field.Settings.ToDictionary(kv => kv.Key, kv => kv.Value)); + } + + public ContentPartFieldDefinitionBuilder WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public ContentPartFieldDefinitionBuilder WithDisplayName(string displayName) { + _settings[ContentPartFieldDefinition.DisplayNameKey] = displayName; + return this; + } + + public abstract string Name { get; } + public abstract string FieldType { get; } + public abstract string PartName { get; } + + public abstract ContentPartFieldDefinitionBuilder OfType(ContentFieldDefinition fieldDefinition); + public abstract ContentPartFieldDefinitionBuilder OfType(string fieldType); + + public abstract ContentPartFieldDefinition Build(); + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs new file mode 100644 index 00000000000..e3409071c15 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Builders { + public class ContentTypeDefinitionBuilder { + private string _name; + private string _displayName; + private readonly IList _parts; + private readonly SettingsDictionary _settings; + + public ContentTypeDefinition Current { get; private set; } + + public ContentTypeDefinitionBuilder() + : this(new ContentTypeDefinition(null, null)) { + } + + public ContentTypeDefinitionBuilder(ContentTypeDefinition existing) { + Current = existing; + + if (existing == null) { + _parts = new List(); + _settings = new SettingsDictionary(); + } + else { + _name = existing.Name; + _displayName = existing.DisplayName; + _parts = existing.Parts.ToList(); + _settings = new SettingsDictionary(existing.Settings.ToDictionary(kv => kv.Key, kv => kv.Value)); + } + } + + public ContentTypeDefinition Build() { + return new ContentTypeDefinition(_name, _displayName, _parts, _settings); + } + + public ContentTypeDefinitionBuilder Named(string name) { + _name = name; + return this; + } + + public ContentTypeDefinitionBuilder DisplayedAs(string displayName) { + _displayName = displayName; + return this; + } + + public ContentTypeDefinitionBuilder WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public ContentTypeDefinitionBuilder RemovePart(string partName) { + var existingPart = _parts.SingleOrDefault(x => x.PartDefinition.Name == partName); + if (existingPart != null) { + _parts.Remove(existingPart); + } + return this; + } + + public ContentTypeDefinitionBuilder WithPart(string partName) { + return WithPart(partName, configuration => { }); + } + + public ContentTypeDefinitionBuilder WithPart(string partName, Action configuration) { + return WithPart(new ContentPartDefinition(partName), configuration); + } + + public ContentTypeDefinitionBuilder WithPart(ContentPartDefinition partDefinition, Action configuration) { + var existingPart = _parts.SingleOrDefault(x => x.PartDefinition.Name == partDefinition.Name); + if (existingPart != null) { + _parts.Remove(existingPart); + } + else { + existingPart = new ContentTypePartDefinition(partDefinition, new SettingsDictionary()); + } + var configurer = new PartConfigurerImpl(existingPart); + configuration(configurer); + _parts.Add(configurer.Build()); + return this; + } + + class PartConfigurerImpl : ContentTypePartDefinitionBuilder { + private readonly ContentPartDefinition _partDefinition; + + public ContentTypePartDefinition Current { get; private set; } + + public PartConfigurerImpl(ContentTypePartDefinition part) + : base(part) { + Current = part; + _partDefinition = part.PartDefinition; + } + + public override ContentTypePartDefinition Build() { + return new ContentTypePartDefinition(_partDefinition, _settings); + } + } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypePartDefinitionBuilder.cs b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypePartDefinitionBuilder.cs new file mode 100644 index 00000000000..0b8d8a9f04d --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Builders/ContentTypePartDefinitionBuilder.cs @@ -0,0 +1,24 @@ +using System.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Builders { + public abstract class ContentTypePartDefinitionBuilder { + protected readonly SettingsDictionary _settings; + + protected ContentTypePartDefinitionBuilder(ContentTypePartDefinition part) { + Name = part.PartDefinition.Name; + TypeName = part.ContentTypeDefinition != null ? part.ContentTypeDefinition.Name : default(string); + _settings = new SettingsDictionary(part.Settings.ToDictionary(kv => kv.Key, kv => kv.Value)); + } + + public string Name { get; private set; } + public string TypeName { get; private set; } + + public ContentTypePartDefinitionBuilder WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public abstract ContentTypePartDefinition Build(); + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionManager.cs b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionManager.cs new file mode 100644 index 00000000000..fc1e5dcfad4 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using OrchardVNext.ContentManagement.MetaData.Builders; +using OrchardVNext.ContentManagement.MetaData.Models; +using OrchardVNext.Utility.Extensions; + +namespace OrchardVNext.ContentManagement.MetaData { + public interface IContentDefinitionManager : IDependency { + IEnumerable ListTypeDefinitions(); + IEnumerable ListPartDefinitions(); + IEnumerable ListFieldDefinitions(); + + ContentTypeDefinition GetTypeDefinition(string name); + ContentPartDefinition GetPartDefinition(string name); + void DeleteTypeDefinition(string name); + void DeletePartDefinition(string name); + + void StoreTypeDefinition(ContentTypeDefinition contentTypeDefinition); + void StorePartDefinition(ContentPartDefinition contentPartDefinition); + } + + public static class ContentDefinitionManagerExtensions{ + public static void AlterTypeDefinition(this IContentDefinitionManager manager, string name, Action alteration) { + var typeDefinition = manager.GetTypeDefinition(name) ?? new ContentTypeDefinition(name, name.CamelFriendly()); + var builder = new ContentTypeDefinitionBuilder(typeDefinition); + alteration(builder); + manager.StoreTypeDefinition(builder.Build()); + } + public static void AlterPartDefinition(this IContentDefinitionManager manager, string name, Action alteration) { + var partDefinition = manager.GetPartDefinition(name) ?? new ContentPartDefinition(name); + var builder = new ContentPartDefinitionBuilder(partDefinition); + alteration(builder); + manager.StorePartDefinition(builder.Build()); + } + } +} + diff --git a/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionReader.cs b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionReader.cs new file mode 100644 index 00000000000..778aee6e296 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionReader.cs @@ -0,0 +1,18 @@ +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Builders; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData { + public interface IContentDefinitionReader : IDependency { + void Merge(XElement source, ContentTypeDefinitionBuilder builder); + void Merge(XElement source, ContentPartDefinitionBuilder builder); + } + + public static class ContentDefinitionReaderExtensions { + public static ContentTypeDefinition Import(this IContentDefinitionReader reader, XElement source) { + var target = new ContentTypeDefinitionBuilder(); + reader.Merge(source, target); + return target.Build(); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionWriter.cs b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionWriter.cs new file mode 100644 index 00000000000..88866df7e85 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/IContentDefinitionWriter.cs @@ -0,0 +1,9 @@ +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData { + public interface IContentDefinitionWriter : IDependency{ + XElement Export(ContentTypeDefinition typeDefinition); + XElement Export(ContentPartDefinition partDefinition); + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/ContentFieldDefinition.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentFieldDefinition.cs new file mode 100644 index 00000000000..85dc5f7548e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentFieldDefinition.cs @@ -0,0 +1,9 @@ +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class ContentFieldDefinition { + public ContentFieldDefinition(string name) { + Name = name; + } + + public string Name { get; private set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartDefinition.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartDefinition.cs new file mode 100644 index 00000000000..e1445f3f2be --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartDefinition.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.Utility.Extensions; + +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class ContentPartDefinition { + public ContentPartDefinition(string name, IEnumerable fields, SettingsDictionary settings) { + Name = name; + Fields = fields.ToReadOnlyCollection(); + Settings = settings; + } + + public ContentPartDefinition(string name) { + Name = name; + Fields = Enumerable.Empty(); + Settings = new SettingsDictionary(); + } + + public string Name { get; private set; } + public IEnumerable Fields { get; private set; } + public SettingsDictionary Settings { get; private set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartFieldDefinition.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartFieldDefinition.cs new file mode 100644 index 00000000000..883d77cf041 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentPartFieldDefinition.cs @@ -0,0 +1,31 @@ +using OrchardVNext.Utility.Extensions; + +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class ContentPartFieldDefinition { + public const string DisplayNameKey = "DisplayName"; + + public ContentPartFieldDefinition(string name) { + Name = name; + FieldDefinition = new ContentFieldDefinition(null); + Settings = new SettingsDictionary(); + } + public ContentPartFieldDefinition( ContentFieldDefinition contentFieldDefinition, string name, SettingsDictionary settings) { + Name = name; + FieldDefinition = contentFieldDefinition; + Settings = settings; + } + + public string Name { get; private set; } + + public string DisplayName { + get { + // if none is defined, generate one from the technical name + return Settings.ContainsKey(DisplayNameKey) ? Settings[DisplayNameKey] : Name.CamelFriendly(); + } + set { Settings[DisplayNameKey] = value; } + } + + public ContentFieldDefinition FieldDefinition { get; private set; } + public SettingsDictionary Settings { get; private set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypeDefinition.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypeDefinition.cs new file mode 100644 index 00000000000..071237bf8e3 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypeDefinition.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using OrchardVNext.Utility.Extensions; + +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class ContentTypeDefinition { + public ContentTypeDefinition(string name, string displayName, IEnumerable parts, SettingsDictionary settings) { + Name = name; + DisplayName = displayName; + Parts = parts.ToReadOnlyCollection(); + Settings = settings; + } + + public ContentTypeDefinition(string name, string displayName) { + Name = name; + DisplayName = displayName; + Parts = Enumerable.Empty(); + Settings = new SettingsDictionary(); + } + + [StringLength(128)] + public string Name { get; private set; } + [Required, StringLength(1024)] + public string DisplayName { get; private set; } + public IEnumerable Parts { get; private set; } + public SettingsDictionary Settings { get; private set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypePartDefinition.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypePartDefinition.cs new file mode 100644 index 00000000000..c5bf3d9c534 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/ContentTypePartDefinition.cs @@ -0,0 +1,17 @@ +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class ContentTypePartDefinition { + + public ContentTypePartDefinition(ContentPartDefinition contentPartDefinition, SettingsDictionary settings) { + PartDefinition = contentPartDefinition; + Settings = settings; + } + + public ContentTypePartDefinition() { + Settings = new SettingsDictionary(); + } + + public ContentPartDefinition PartDefinition { get; private set; } + public SettingsDictionary Settings { get; private set; } + public ContentTypeDefinition ContentTypeDefinition { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Models/SettingsDictionary.cs b/src/OrchardVNext/ContentManagement/MetaData/Models/SettingsDictionary.cs new file mode 100644 index 00000000000..da9124f737b --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Models/SettingsDictionary.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace OrchardVNext.ContentManagement.MetaData.Models { + public class SettingsDictionary : Dictionary { + public SettingsDictionary() { } + public SettingsDictionary(IDictionary dictionary) : base(dictionary) { } + + public T TryGetModel(string key) where T : class { + throw new NotImplementedException("TODO: Get this working!"); + //var binder = new DefaultModelBinder(); + //var controllerContext = new ControllerContext(); + //var context = new ModelBindingContext { + // ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(T)), + // ModelName = key, + // ValueProvider = new DictionaryValueProvider(this, null) + //}; + //return (T)binder.BindModel(controllerContext, context); + + } + + public T TryGetModel() where T : class { + return TryGetModel(typeof(T).Name); + } + + public T GetModel() where T : class, new() { + return GetModel(typeof(T).Name); + } + + public T GetModel(string key) where T : class, new() { + return TryGetModel(key) ?? new T(); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionReader.cs b/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionReader.cs new file mode 100644 index 00000000000..45e73daba1e --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionReader.cs @@ -0,0 +1,114 @@ +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Builders; +using OrchardVNext.Validation; + +namespace OrchardVNext.ContentManagement.MetaData.Services { + /// + /// The content definition reader is used to import both content type and content part definitions from a XML format. + /// + public class ContentDefinitionReader : IContentDefinitionReader { + /// + /// The settings formatter to be used to convert the settings between a XML format and a dictionary. + /// + private readonly ISettingsFormatter _settingsFormatter; + + /// + /// Initializes a new instance of the class. + /// + /// The settings formatter to be used to convert the settings between a dictionary and an XML format. + public ContentDefinitionReader(ISettingsFormatter settingsFormatter) { + Argument.ThrowIfNull(settingsFormatter, "settingsFormatter"); + + _settingsFormatter = settingsFormatter; + } + + /// + /// Merges a given content type definition provided in a XML format into a content type definition builder. + /// + /// The XML content type definition. + /// The content type definition builder. + public void Merge(XElement element, ContentTypeDefinitionBuilder contentTypeDefinitionBuilder) { + Argument.ThrowIfNull(element, "element"); + Argument.ThrowIfNull(contentTypeDefinitionBuilder, "contentTypeDefinitionBuilder"); + + // Merge name + contentTypeDefinitionBuilder.Named(XmlConvert.DecodeName(element.Name.LocalName)); + + + // Merge display name + if (element.Attribute("DisplayName") != null) { + contentTypeDefinitionBuilder.DisplayedAs(element.Attribute("DisplayName").Value); + } + + // Merge settings + foreach (var setting in _settingsFormatter.Map(element)) { + contentTypeDefinitionBuilder.WithSetting(setting.Key, setting.Value); + } + + // Merge parts + foreach (var iter in element.Elements()) { + var partElement = iter; + var partName = XmlConvert.DecodeName(partElement.Name.LocalName); + if (partName == "remove") { + var nameAttribute = partElement.Attribute("name"); + if (nameAttribute != null) { + contentTypeDefinitionBuilder.RemovePart(nameAttribute.Value); + } + } + else { + contentTypeDefinitionBuilder.WithPart( + partName, + partBuilder => { + foreach (var setting in _settingsFormatter.Map(partElement)) { + partBuilder.WithSetting(setting.Key, setting.Value); + } + }); + } + } + } + + /// + /// Merges a given content part definition provided in a XML format into a content part definition builder. + /// + /// The XML content type definition. + /// The content part definition builder. + public void Merge(XElement element, ContentPartDefinitionBuilder contentPartDefinitionBuilder) { + Argument.ThrowIfNull(element, "element"); + Argument.ThrowIfNull(contentPartDefinitionBuilder, "contentPartDefinitionBuilder"); + + // Merge name + contentPartDefinitionBuilder.Named(XmlConvert.DecodeName(element.Name.LocalName)); + + // Merge settings + foreach (var setting in _settingsFormatter.Map(element)) { + contentPartDefinitionBuilder.WithSetting(setting.Key, setting.Value); + } + + // Merge fields + foreach (var iter in element.Elements()) { + var fieldElement = iter; + var fieldParameters = XmlConvert.DecodeName(fieldElement.Name.LocalName).Split(new[] { '.' }, 2); + var fieldName = fieldParameters.FirstOrDefault(); + var fieldType = fieldParameters.Skip(1).FirstOrDefault(); + if (fieldName == "remove") { + var nameAttribute = fieldElement.Attribute("name"); + if (nameAttribute != null) { + contentPartDefinitionBuilder.RemoveField(nameAttribute.Value); + } + } + else { + contentPartDefinitionBuilder.WithField( + fieldName, + fieldBuilder => { + fieldBuilder.OfType(fieldType); + foreach (var setting in _settingsFormatter.Map(fieldElement)) { + fieldBuilder.WithSetting(setting.Key, setting.Value); + } + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs b/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs new file mode 100644 index 00000000000..19bae56d177 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs @@ -0,0 +1,77 @@ +using System.Xml; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; +using OrchardVNext.Validation; + +namespace OrchardVNext.ContentManagement.MetaData.Services { + /// + /// The content definition writer is used to export both content type and content part definitions to a XML format. + /// + public class ContentDefinitionWriter : IContentDefinitionWriter { + /// + /// The settings formatter to be used to convert the settings between a dictionary and an XML format. + /// + private readonly ISettingsFormatter _settingsFormatter; + + /// + /// Initializes a new instance of the class. + /// + /// The settings formatter to be used to convert the settings between a dictionary and an XML format. + public ContentDefinitionWriter(ISettingsFormatter settingsFormatter) { + Argument.ThrowIfNull(settingsFormatter, nameof(settingsFormatter)); + + _settingsFormatter = settingsFormatter; + } + + /// + /// Exports a content type definition to a XML format. + /// + /// The type definition to be exported. + /// The content type definition in an XML format. + public XElement Export(ContentTypeDefinition contentTypeDefinition) { + Argument.ThrowIfNull(contentTypeDefinition, nameof(contentTypeDefinition)); + + var typeElement = NewElement(contentTypeDefinition.Name, contentTypeDefinition.Settings); + if (typeElement.Attribute("DisplayName") == null && contentTypeDefinition.DisplayName != null) { + typeElement.Add(new XAttribute("DisplayName", contentTypeDefinition.DisplayName)); + } + + foreach (var typePart in contentTypeDefinition.Parts) { + typeElement.Add(NewElement(typePart.PartDefinition.Name, typePart.Settings)); + } + + return typeElement; + } + + /// + /// Exports a content part definition to a XML format. + /// + /// The part definition to be exported. + /// The content part definition in a XML format. + public XElement Export(ContentPartDefinition contentPartDefinition) { + Argument.ThrowIfNull(contentPartDefinition, nameof(contentPartDefinition)); + + var partElement = NewElement(contentPartDefinition.Name, contentPartDefinition.Settings); + foreach (var partField in contentPartDefinition.Fields) { + var attributeName = string.Format("{0}.{1}", partField.Name, partField.FieldDefinition.Name); + var partFieldElement = NewElement(attributeName, partField.Settings); + partElement.Add(partFieldElement); + } + + return partElement; + } + + /// + /// Builds a new XML element with a given name and a settings dictionary. + /// + /// The name of the element to be mapped to XML. + /// The settings dictionary to be used as the element's attributes. + /// The new XML element. + private XElement NewElement(string name, SettingsDictionary settings) { + XElement element = _settingsFormatter.Map(settings); + element.Name = XmlConvert.EncodeLocalName(name); + + return element; + } + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Services/ISettingsFormatter.cs b/src/OrchardVNext/ContentManagement/MetaData/Services/ISettingsFormatter.cs new file mode 100644 index 00000000000..9ca04051808 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Services/ISettingsFormatter.cs @@ -0,0 +1,23 @@ +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Services { + /// + /// Abstraction to manage settings metadata on a content. + /// + public interface ISettingsFormatter : IDependency { + /// + /// Maps an XML element to a settings dictionary. + /// + /// The XML element to be mapped. + /// The settings dictionary. + SettingsDictionary Map(XElement element); + + /// + /// Maps a settings dictionary to an XML element. + /// + /// The settings dictionary. + /// The XML element. + XElement Map(SettingsDictionary settingsDictionary); + } +} diff --git a/src/OrchardVNext/ContentManagement/MetaData/Services/SettingsFormatter.cs b/src/OrchardVNext/ContentManagement/MetaData/Services/SettingsFormatter.cs new file mode 100644 index 00000000000..0aa46a8fbf7 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/MetaData/Services/SettingsFormatter.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.MetaData.Models; + +namespace OrchardVNext.ContentManagement.MetaData.Services { + /// + /// Abstraction to manage settings metadata on a content. + /// + public class SettingsFormatter : ISettingsFormatter { + /// + /// Maps an XML element to a settings dictionary. + /// + /// The XML element to be mapped. + /// The settings dictionary. + public SettingsDictionary Map(XElement element) { + if (element == null) { + return new SettingsDictionary(); + } + + return new SettingsDictionary( + element.Attributes() + .ToDictionary(attr => XmlConvert.DecodeName(attr.Name.LocalName), attr => attr.Value)); + } + + /// + /// Maps a settings dictionary to an XML element. + /// + /// The settings dictionary. + /// The XML element. + public XElement Map(SettingsDictionary settingsDictionary) { + if (settingsDictionary == null) { + return new XElement("settings"); + } + + return new XElement( + "settings", + settingsDictionary + .Where(kv => kv.Value != null) + .Select(kv => new XAttribute(XmlConvert.EncodeLocalName(kv.Key), kv.Value))); + } + } +} diff --git a/src/OrchardVNext/ContentManagement/Records/ContentItemRecord.cs b/src/OrchardVNext/ContentManagement/Records/ContentItemRecord.cs new file mode 100644 index 00000000000..d1d705eaf83 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/ContentItemRecord.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using OrchardVNext.Data; + +namespace OrchardVNext.ContentManagement.Records { + [Persistent] + public class ContentItemRecord : DocumentRecord { + public ContentItemRecord() { + // ReSharper disable DoNotCallOverridableMethodsInConstructor + Versions = new List(); + // ReSharper restore DoNotCallOverridableMethodsInConstructor + } + + public virtual ContentTypeRecord ContentType { get; set; } + public virtual IList Versions { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Records/ContentItemVersionRecord.cs b/src/OrchardVNext/ContentManagement/Records/ContentItemVersionRecord.cs new file mode 100644 index 00000000000..d0ac4a7d6bf --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/ContentItemVersionRecord.cs @@ -0,0 +1,12 @@ +using OrchardVNext.Data; + +namespace OrchardVNext.ContentManagement.Records { + [Persistent] + public class ContentItemVersionRecord : DocumentRecord { + public ContentItemRecord ContentItemRecord { get; set; } + public int Number { get; set; } + + public bool Published { get; set; } + public bool Latest { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Records/ContentPartRecord.cs b/src/OrchardVNext/ContentManagement/Records/ContentPartRecord.cs new file mode 100644 index 00000000000..e51e2b863a7 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/ContentPartRecord.cs @@ -0,0 +1,6 @@ +namespace OrchardVNext.ContentManagement.Records { + public abstract class ContentPartRecord { + public virtual int Id { get; set; } + public virtual ContentItemRecord ContentItemRecord { get; set; } + } +} diff --git a/src/OrchardVNext/ContentManagement/Records/ContentPartVersionRecord.cs b/src/OrchardVNext/ContentManagement/Records/ContentPartVersionRecord.cs new file mode 100644 index 00000000000..6e0805106e6 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/ContentPartVersionRecord.cs @@ -0,0 +1,5 @@ +namespace OrchardVNext.ContentManagement.Records { + public abstract class ContentPartVersionRecord : ContentPartRecord { + public virtual ContentItemVersionRecord ContentItemVersionRecord { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Records/ContentTypeRecord.cs b/src/OrchardVNext/ContentManagement/Records/ContentTypeRecord.cs new file mode 100644 index 00000000000..239dfee5c13 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/ContentTypeRecord.cs @@ -0,0 +1,11 @@ +using OrchardVNext.Data; + +namespace OrchardVNext.ContentManagement.Records { + [Persistent] + public class ContentTypeRecord : DocumentRecord { + public string Name { + get { return this.RetrieveValue(x => x.Name); } + set { this.StoreValue(x => x.Name, value); } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/Records/DocumentRecord.cs b/src/OrchardVNext/ContentManagement/Records/DocumentRecord.cs new file mode 100644 index 00000000000..87c6ffe8ea0 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/Records/DocumentRecord.cs @@ -0,0 +1,102 @@ +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using OrchardVNext.ContentManagement.FieldStorage.InfosetStorage; +using OrchardVNext.Data.Conventions; + +namespace OrchardVNext.ContentManagement.Records { + public class DocumentRecord { + public DocumentRecord() { + Infoset = new Infoset(); + VersionInfoset = new Infoset(); + } + + public int Id { get; set; } + + [StringLengthMax] + public string Data { get { return Infoset.Data; } set { Infoset.Data = value; } } + public Infoset Infoset { get; set; } + public Infoset VersionInfoset { get; set; } + + + public string Get(string fieldName) { + return Get(fieldName, null); + } + + public string Get(string fieldName, string valueName) { + return Get(typeof(TPart).Name, fieldName, valueName, typeof(TPart).GetTypeInfo().IsAssignableFrom(typeof(ContentItemVersionRecord).GetTypeInfo())); + } + + public string Get(string partName, string fieldName) { + return Get(partName, fieldName, null, false); + } + + public string GetVersioned(string partName, string fieldName) { + return Get(partName, fieldName, null, true); + } + + public string Get(string partName, string fieldName, string valueName, bool versionable = false) { + + var element = versionable ? VersionInfoset.Element : Infoset.Element; + + var partElement = element.Element(XmlConvert.EncodeName(partName)); + if (partElement == null) { + return null; + } + var fieldElement = partElement.Element(XmlConvert.EncodeName(fieldName)); + if (fieldElement == null) { + return null; + } + if (string.IsNullOrEmpty(valueName)) { + return fieldElement.Value; + } + var valueAttribute = fieldElement.Attribute(XmlConvert.EncodeName(valueName)); + if (valueAttribute == null) { + return null; + } + return valueAttribute.Value; + } + + public void Set(string fieldName, string valueName, string value) { + Set(fieldName, value); + } + + public void Set(string fieldName, string value) { + Set(typeof(TPart).Name, fieldName, null, value, typeof(TPart).GetTypeInfo().IsAssignableFrom(typeof(ContentItemVersionRecord).GetTypeInfo())); + } + + public void Set(string partName, string fieldName, string value) { + Set(partName, fieldName, null, value, false); + } + + public void SetVersioned(string partName, string fieldName, string value) { + Set(partName, fieldName, null, value, true); + } + + public void Set(string partName, string fieldName, string valueName, string value, bool versionable = false) { + + var element = versionable ? VersionInfoset.Element : Infoset.Element; + + var encodedPartName = XmlConvert.EncodeName(partName); + var partElement = element.Element(encodedPartName); + if (partElement == null) { + partElement = new XElement(encodedPartName); + Infoset.Element.Add(partElement); + } + + var encodedFieldName = XmlConvert.EncodeName(fieldName); + var fieldElement = partElement.Element(encodedFieldName); + if (fieldElement == null) { + fieldElement = new XElement(encodedFieldName); + partElement.Add(fieldElement); + } + + if (string.IsNullOrEmpty(valueName)) { + fieldElement.Value = value ?? ""; + } + else { + fieldElement.SetAttributeValue(XmlConvert.EncodeName(valueName), value); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/ContentManagement/XmlHelper.cs b/src/OrchardVNext/ContentManagement/XmlHelper.cs new file mode 100644 index 00000000000..cd13fe92026 --- /dev/null +++ b/src/OrchardVNext/ContentManagement/XmlHelper.cs @@ -0,0 +1,360 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Xml; +using System.Xml.Linq; +using System.Reflection; +using OrchardVNext.Utility; + +namespace OrchardVNext.ContentManagement { + public static class XmlHelper { + /// + /// Like Add, but chainable. + /// + /// The parent element. + /// The elements to add. + /// Itself + public static XElement AddEl(this XElement el, params XElement[] children) { + el.Add(children.Cast()); + return el; + } + + /// + /// Gets the string value of an attribute, and null if the attribute doesn't exist. + /// + /// The element. + /// The name of the attribute. + /// The string value of the attribute if it exists, null otherwise. + public static string Attr(this XElement el, string name) { + var attr = el.Attribute(name); + return attr == null ? null : attr.Value; + } + + /// + /// Gets a typed value from an attribute. + /// + /// The type of the value + /// The element. + /// The name of the attribute. + /// The attribute value + public static T Attr(this XElement el, string name) { + + var attr = el.Attribute(name); + return attr == null ? default(T) : Parse(attr.Value); + } + + /// + /// Sets an attribute value. This is chainable. + /// + /// The type of the value. + /// The element. + /// The attribute name. + /// The value to set. + /// Itself + public static XElement Attr(this XElement el, string name, T value) { + el.SetAttributeValue(name, ToString(value)); + return el; + } + + /// + /// Returns the text contents of a child element. + /// + /// The parent element. + /// The name of the child element. + /// The text for the child element, and null if it doesn't exist. + public static string El(this XElement el, string name) { + var childElement = el.Element(name); + return childElement == null ? null : childElement.Value; + } + + /// + /// Creates and sets the value of a child element. This is chainable. + /// + /// The type of the value. + /// The parent element. + /// The name of the child element. + /// The value to set. + /// Itself + public static XElement El(this XElement el, string name, T value) { + el.SetElementValue(name, value); + return el; + } + + /// + /// Sets a property value from an attribute of the same name. + /// + /// The type of the target object. + /// The type of the target property + /// The element. + /// The target object. + /// The property expression. + /// Itself + public static XElement FromAttr(this XElement el, TTarget target, + Expression> targetExpression) { + + if (target == null) return el; + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + var attr = el.Attribute(name); + + if (attr == null) return el; + propertyInfo.SetValue(target, el.Attr(name), null); + return el; + } + + /// + /// Sets an attribute with the value of a property of the same name. + /// + /// The type of the object. + /// The type of the property. + /// The element. + /// The object. + /// The property expression. + /// Itself + public static XElement ToAttr(this XElement el, TTarget target, + Expression> targetExpression) { + + if (target == null) return el; + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + var val = (TProperty)propertyInfo.GetValue(target, null); + + el.Attr(name, ToString(val)); + return el; + } + + /// + /// Gets the text value of an element as the specified type. + /// + /// The type to parse the element as. + /// The element. + /// The value of the element as type TValue. + public static TValue Val(this XElement el) { + return Parse(el.Value); + } + + /// + /// Sets the value of an element. + /// + /// The type of the value to set. + /// The element. + /// The value. + /// The element. + public static XElement Val(this XElement el, TValue value) { + el.SetValue(ToString(value)); + return el; + } + + /// + /// Serializes the provided value as a string. + /// + /// The type of the value. + /// The value. + /// The string representation of the value. + public static string ToString(T value) { + var type = typeof(T); + if (type == typeof(string)) { + return Convert.ToString(value); + } + if ((!type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null) && + value == null && + type != typeof(string)) { + + return "null"; + } + + if (type == typeof(DateTime) || type == typeof(DateTime?)) { + return XmlConvert.ToString(Convert.ToDateTime(value), + XmlDateTimeSerializationMode.Utc); + } + + if (type == typeof(bool) || + type == typeof(bool?)) { + return Convert.ToBoolean(value) ? "true" : "false"; + } + + if (type == typeof(int) || + type == typeof(int?) || + type == typeof(long) || + type == typeof(long?)) { + + return Convert.ToInt64(value).ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(double) || + type == typeof(double?)) { + + var doubleValue = (double)(object)value; + if (double.IsPositiveInfinity(doubleValue)) { + return "infinity"; + } + if (double.IsNegativeInfinity(doubleValue)) { + return "-infinity"; + } + return doubleValue.ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(float) || + type == typeof(float?)) { + + var floatValue = (float)(object)value; + if (float.IsPositiveInfinity(floatValue)) { + return "infinity"; + } + if (float.IsNegativeInfinity(floatValue)) { + return "-infinity"; + } + return floatValue.ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(decimal) || + type == typeof(decimal?)) { + + var decimalValue = Convert.ToDecimal(value); + return decimalValue.ToString(CultureInfo.InvariantCulture); + } + + if (type.GetTypeInfo().IsEnum) { + return value.ToString(); + } + + throw new NotSupportedException(String.Format("Could not handle type {0}", type.Name)); + } + + /// + /// Parses a string value as the provided type. + /// + /// The destination type + /// The string representation of the value to parse. + /// The parsed value with type T. + public static T Parse(string value) { + var type = typeof(T); + + if (type == typeof(string)) { + return (T)(object)value; + } + if (value == null || + "null".Equals(value, StringComparison.Ordinal) && + ((!type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null))) { + + return default(T); + } + + if ("infinity".Equals(value, StringComparison.Ordinal)) { + if (type == typeof(float) || type == typeof(float?)) return (T)(object)float.PositiveInfinity; + if (type == typeof(double) || type == typeof(double?)) return (T)(object)double.PositiveInfinity; + throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name)); + } + if ("-infinity".Equals(value, StringComparison.Ordinal)) { + if (type == typeof(float)) return (T)(object)float.NegativeInfinity; + if (type == typeof(double)) return (T)(object)double.NegativeInfinity; + throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name)); + } + if (type == typeof(int) || type == typeof(int?)) { + return (T)(object)int.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(long) || type == typeof(long?)) { + return (T)(object)long.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(bool) || type == typeof(bool?)) { + return (T)(object)value.Equals("true", StringComparison.Ordinal); + } + if (type == typeof(DateTime) || type == typeof(DateTime?)) { + return (T)(object)XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Utc); + } + if (type == typeof(double) || type == typeof(double?)) { + return (T)(object)double.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(float) || type == typeof(float?)) { + return (T)(object)float.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(decimal) || type == typeof(decimal?)) { + return (T)(object)decimal.Parse(value, CultureInfo.InvariantCulture); + } + + if (type.GetTypeInfo().IsEnum) { + return (T)Enum.Parse(type, value); + } + + throw new NotSupportedException(String.Format("Could not handle type {0}", type.Name)); + } + + /// + /// Gives context to an XElement, enabling chained property operations. + /// + /// The type of the context. + /// The element. + /// The context. + /// The element with context. + public static XElementWithContext With(this XElement el, TContext context) { + return new XElementWithContext(el, context); + } + + /// + /// A wrapper for XElement, with context, for strongly-typed manipulation + /// of an XElement. + /// + /// The type of the context. + public class XElementWithContext { + public XElementWithContext(XElement element, TContext context) { + Element = element; + Context = context; + } + + public XElement Element { get; private set; } + public TContext Context { get; private set; } + + public static implicit operator XElement(XElementWithContext elementWithContext) { + return elementWithContext.Element; + } + + /// + /// Replaces the current context with a new one, enabling chained action on different objects. + /// + /// The type of the new context. + /// The new context. + /// A new XElementWithContext, that has the new context. + public XElementWithContext With(TNewContext context) { + return new XElementWithContext(Element, context); + } + + /// + /// Sets the value of a context property as an attribute of the same name on the element. + /// + /// The type of the property. + /// The property expression. + /// Itself + public XElementWithContext ToAttr( + Expression> targetExpression) { + Element.ToAttr(Context, targetExpression); + return this; + } + + /// + /// Gets an attribute on the element and sets the property of the same name on the context with its value. + /// + /// The type of the property. + /// The property expression. + /// Itself + public XElementWithContext FromAttr( + Expression> targetExpression) { + Element.FromAttr(Context, targetExpression); + return this; + } + + /// + /// Evaluates an attribute from an expression. + /// It's a nice strongly-typed way to read attributes. + /// + /// The type of the property. + /// The property expression. + /// The attribute, ready to be cast. + public TProperty Attr(Expression> expression) { + var propertyInfo = ReflectionHelper.GetPropertyInfo(expression); + var name = propertyInfo.Name; + return Element.Attr(name); + } + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/ContentStorageProvider.cs b/src/OrchardVNext/Data/ContentStorageProvider.cs new file mode 100644 index 00000000000..fa52859da01 --- /dev/null +++ b/src/OrchardVNext/Data/ContentStorageProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data { + public class ContentStorageProvider : IContentStorageProvider { + private readonly IContentDocumentStore _contentDocumentStore; + private readonly IContentIndexProvider _contentIndexProvider; + + public ContentStorageProvider(IContentDocumentStore contentDocumentStore, + IContentIndexProvider contentIndexProvider) { + _contentDocumentStore = contentDocumentStore; + _contentIndexProvider = contentIndexProvider; + } + + public void Store(T document) where T : DocumentRecord { + _contentDocumentStore.Store(document); + _contentIndexProvider.Index(document); + } + + public void Remove(T document) where T : DocumentRecord { + _contentDocumentStore.Remove(document); + _contentIndexProvider.DeIndex(document); + } + + public IEnumerable Query() where T : DocumentRecord { + return _contentIndexProvider.Query(); + } + + public IEnumerable Query(Expression> filter) where T : DocumentRecord { + return _contentIndexProvider.Query(filter); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/Conventions/StringLengthConvention.cs b/src/OrchardVNext/Data/Conventions/StringLengthConvention.cs new file mode 100644 index 00000000000..e6588942756 --- /dev/null +++ b/src/OrchardVNext/Data/Conventions/StringLengthConvention.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrchardVNext.Data.Conventions { + public class StringLengthMaxAttribute : StringLengthAttribute { + public StringLengthMaxAttribute() : base(10000) { + // 10000 is an arbitrary number large enough to be in the nvarchar(max) range + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/DataContext.cs b/src/OrchardVNext/Data/EF/DataContext.cs new file mode 100644 index 00000000000..26b12d6d611 --- /dev/null +++ b/src/OrchardVNext/Data/EF/DataContext.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.ChangeTracking; +using Microsoft.Data.Entity.Infrastructure; +using OrchardVNext.Environment.Configuration; + +namespace OrchardVNext.Data.EF { + public interface IDataContext { + DataContext Context { get; } + } + + public class DataContext : DbContext, IDataContext { + private readonly ShellSettings _shellSettings; + private readonly IDbContextFactoryHolder _dbContextFactoryHolder; + private readonly IAssemblyProvider _assemblyProvider; + + private readonly Guid _instanceId; + + public DataContext( + ShellSettings shellSettings, + IDbContextFactoryHolder dbContextFactoryHolder, + IAssemblyProvider assemblyProvider) { + + _shellSettings = shellSettings; + _dbContextFactoryHolder = dbContextFactoryHolder; + _assemblyProvider = assemblyProvider; + _instanceId = Guid.NewGuid(); + } + + public DataContext Context => this; + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + Logger.TraceInformation("[{0}]: Mapping Records to DB Context", GetType().Name); + var sw = Stopwatch.StartNew(); + + var entityMethod = modelBuilder.GetType().GetRuntimeMethod("Entity", new Type[0]); + + foreach (var assembly in _assemblyProvider.CandidateAssemblies.Distinct()) { + // Keep persistent attribute, but also introduce a convention like ContentPart + var entityTypes = assembly + .GetTypes() + .Where(t => + t.GetTypeInfo().GetCustomAttributes(true) + .Any()); + + foreach (var type in entityTypes) { + Logger.Debug("Mapping record {0}", type.FullName); + + entityMethod.MakeGenericMethod(type) + .Invoke(modelBuilder, new object[0]); + } + } + + sw.Stop(); + Logger.TraceInformation("[{0}]: Records Mapped in {1}ms", GetType().Name, sw.ElapsedMilliseconds); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + _dbContextFactoryHolder.Configure(optionsBuilder); + } + + public override EntityEntry Add([NotNull]TEntity entity) { + var entry = base.Add(entity); + SaveChanges(); + return entry; + } + + public override EntityEntry Add([NotNull]object entity) { + var entry = base.Add(entity); + SaveChanges(); + return entry; + } + + public override EntityEntry Remove([NotNull]TEntity entity) { + var entry = base.Remove(entity); + SaveChanges(); + return entry; + } + + public override EntityEntry Remove([NotNull]object entity) { + var entry = base.Remove(entity); + SaveChanges(); + return entry; + } + + //public override int SaveChanges() { + // var entriesToSave = ChangeTracker.StateManager.Entries + // .Where(e => e.EntityState == EntityState.Added + // || e.EntityState == EntityState.Modified + // || e.EntityState == EntityState.Deleted) + // .Select(e => e.PrepareToSave()) // do I need this line!? + // .ToList(); + + // // TODO: Call Api to store them in a seperate querying/indexing store i.e. lucene. + // // Needs to be async? or place in a messaging queue of some sort. + + // return base.SaveChanges(); + //} + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/EFContentDocumentStore.cs b/src/OrchardVNext/Data/EF/EFContentDocumentStore.cs new file mode 100644 index 00000000000..b8f6b1742bf --- /dev/null +++ b/src/OrchardVNext/Data/EF/EFContentDocumentStore.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data.EF +{ + public class EFContentDocumentStore : IContentDocumentStore { + private readonly DataContext _dataContext; + + public EFContentDocumentStore(DataContext dataContext) { + _dataContext = dataContext; + } + + public void Store(T document) where T : DocumentRecord { + _dataContext.Add(document); + } + + public void Remove(T document) where T : DocumentRecord { + _dataContext.Remove(document); + } + + //public IEnumerable Query() where T : DocumentRecord { + // return _dataContext.Set().AsQueryable(); + //} + + //public IEnumerable Query(Expression> filter) where T : DocumentRecord { + // return _dataContext.Set().Where(filter); + //} + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/EFContentIndexProvider.cs b/src/OrchardVNext/Data/EF/EFContentIndexProvider.cs new file mode 100644 index 00000000000..a48be588b35 --- /dev/null +++ b/src/OrchardVNext/Data/EF/EFContentIndexProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data.EF { + public class EFContentIndexProvider : IContentIndexProvider { + private readonly IEnumerable _contentQueryExpressionHandlers; + private readonly DataContext _dataContext; + + public EFContentIndexProvider(IEnumerable contentQueryExpressionHandlers, + DataContext dataContext) + { + _contentQueryExpressionHandlers = contentQueryExpressionHandlers; + _dataContext = dataContext; + } + + public void Index(T content) where T : DocumentRecord { + // Get Lambda and store this content. + var data = content.Infoset.Data; + + foreach (var handler in _contentQueryExpressionHandlers) { + Expression> filter = handler.OnCreating(); + + var canReduce = filter.CanReduce; + + // TODO - Sanitize in to usefulname + var indexName = filter.Body.ToString(); + Logger.Debug("Adding {0} to index {1}", typeof(T).Name, indexName); + + + //_contentDocumentStore.Query(filter); + } + } + + public void DeIndex(T content) where T : DocumentRecord { + //throw new NotImplementedException(); + } + public IEnumerable Query() where T : DocumentRecord { + return _dataContext.Set().ToList(); + } + + public IEnumerable Query(Expression> filter) where T : DocumentRecord { + return _dataContext.Set().Where(filter).ToList(); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/EFContentStore.cs b/src/OrchardVNext/Data/EF/EFContentStore.cs new file mode 100644 index 00000000000..579591b789a --- /dev/null +++ b/src/OrchardVNext/Data/EF/EFContentStore.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OrchardVNext.ContentManagement; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data.EF { + public class EfContentItemStore : IContentItemStore { + private readonly IContentStorageProvider _contentStorageProvider; + + public EfContentItemStore( + IContentStorageProvider contentStorageProvider) { + _contentStorageProvider = contentStorageProvider; + } + + private readonly Func _query = (versionRecord, id, options) => { + if (options.IsPublished) { + return versionRecord.ContentItemRecord.Id == id && versionRecord.Published; + } + if (options.IsLatest || options.IsDraftRequired) { + return versionRecord.ContentItemRecord.Id == id && versionRecord.Latest; + } + if (options.IsDraft) { + return versionRecord.ContentItemRecord.Id == id && versionRecord.Latest && !versionRecord.Published; + } + if (options.VersionNumber != 0) { + return versionRecord.ContentItemRecord.Id == id && versionRecord.Number == options.VersionNumber; + } + return versionRecord.ContentItemRecord.Id == id; + }; + + public void Store(ContentItem contentItem) { + _contentStorageProvider.Store(contentItem.Record); + _contentStorageProvider.Store(contentItem.VersionRecord); + } + + public ContentItem Get(int id) { + return Get(id, VersionOptions.Published); + } + + public ContentItem Get(int id, VersionOptions options) { + var record = _contentStorageProvider + .Query(x => _query(x, id, options)) + .OrderBy(x => x.Number) + .LastOrDefault(); + + return new ContentItem { VersionRecord = record }; + } + + public IEnumerable GetMany(IEnumerable ids) { + return _contentStorageProvider + .Query(x => ids.Contains(x.Id)) + .Select(x => new ContentItem { VersionRecord = x }); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/IContentQueryExpressionHandler.cs b/src/OrchardVNext/Data/EF/IContentQueryExpressionHandler.cs new file mode 100644 index 00000000000..ae2d9406cd8 --- /dev/null +++ b/src/OrchardVNext/Data/EF/IContentQueryExpressionHandler.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement.Handlers; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data.EF { + public interface IContentQueryExpressionHandler : IDependency { + Expression> OnCreating() where TContent : DocumentRecord; + } + + //public abstract class ContentQueryExpressionHandler : IContentQueryExpressionHandler { + // protected ContentQueryExpressionHandler() { + // Filters = new List(); + // } + + // public List Filters { get; set; } + // protected void OnActivated(Action handler) where TPart : DocumentRecord { + // Filters.Add(new InlineStorageFilter { OnActivated = handler }); + // } + + // Expression> IContentQueryExpressionHandler.OnCreating() + // { + // return c => c.Data == ""; + // } + //} + + //public class ContentQueryExpressionHandlerImpl : ContentQueryExpressionHandler + //{ + // public ContentQueryExpressionHandlerImpl() + // { + // OnCreating<> + // } + //} +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/EF/IDbContextFactoryHolder.cs b/src/OrchardVNext/Data/EF/IDbContextFactoryHolder.cs new file mode 100644 index 00000000000..c223aee9b4e --- /dev/null +++ b/src/OrchardVNext/Data/EF/IDbContextFactoryHolder.cs @@ -0,0 +1,40 @@ +using Microsoft.Data.Entity; +using OrchardVNext.Data.Providers; +using OrchardVNext.Environment.Configuration; +using OrchardVNext.FileSystems.AppData; + +namespace OrchardVNext.Data.EF { + public interface IDbContextFactoryHolder : IDependency { + void Configure(DbContextOptionsBuilder optionsBuilder); + } + + public class DbContextFactoryHolder : IDbContextFactoryHolder { + private readonly ShellSettings _shellSettings; + private readonly IDataServicesProviderFactory _dataServicesProviderFactory; + private readonly IAppDataFolder _appDataFolder; + + public DbContextFactoryHolder( + ShellSettings shellSettings, + IDataServicesProviderFactory dataServicesProviderFactory, + IAppDataFolder appDataFolder) { + _shellSettings = shellSettings; + _dataServicesProviderFactory = dataServicesProviderFactory; + _appDataFolder = appDataFolder; + } + + public void Configure(DbContextOptionsBuilder optionsBuilders) { + var shellPath = _appDataFolder.Combine("Sites", _shellSettings.Name); + _appDataFolder.CreateDirectory(shellPath); + + var shellFolder = _appDataFolder.MapPath(shellPath); + + _dataServicesProviderFactory.CreateProvider( + new DataServiceParameters { + Provider = _shellSettings.DataProvider, + ConnectionString = _shellSettings.DataConnectionString, + DataFolder = shellFolder + }) + .ConfigureContextOptions(optionsBuilders); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/IContentDocumentStore.cs b/src/OrchardVNext/Data/IContentDocumentStore.cs new file mode 100644 index 00000000000..cc3a70645d1 --- /dev/null +++ b/src/OrchardVNext/Data/IContentDocumentStore.cs @@ -0,0 +1,8 @@ +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data { + public interface IContentDocumentStore : IDependency { + void Store(T document) where T : DocumentRecord; + void Remove(T document) where T : DocumentRecord; + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/IContentIndexProvider.cs b/src/OrchardVNext/Data/IContentIndexProvider.cs new file mode 100644 index 00000000000..f3a6b089e75 --- /dev/null +++ b/src/OrchardVNext/Data/IContentIndexProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data { + public interface IContentIndexProvider : IDependency { + void Index(T document) where T : DocumentRecord; + void DeIndex(T document) where T : DocumentRecord; + + IEnumerable Query() where T : DocumentRecord; + IEnumerable Query(Expression> filter) where T : DocumentRecord; + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/IContentStorageProvider.cs b/src/OrchardVNext/Data/IContentStorageProvider.cs new file mode 100644 index 00000000000..c0021c313eb --- /dev/null +++ b/src/OrchardVNext/Data/IContentStorageProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using OrchardVNext.ContentManagement.Records; + +namespace OrchardVNext.Data { + public interface IContentStorageProvider : IDependency { + void Store(T document) where T : DocumentRecord; + void Remove(T document) where T : DocumentRecord; + + IEnumerable Query() where T : DocumentRecord; + IEnumerable Query(Expression> filter) where T : DocumentRecord; + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/IContentStore.cs b/src/OrchardVNext/Data/IContentStore.cs new file mode 100644 index 00000000000..752fb7864e4 --- /dev/null +++ b/src/OrchardVNext/Data/IContentStore.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using OrchardVNext.ContentManagement; + +namespace OrchardVNext.Data { + public interface IContentItemStore : IDependency { + void Store(ContentItem contentItem); + ContentItem Get(int id); + ContentItem Get(int id, VersionOptions options); + IEnumerable GetMany(IEnumerable ids); + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/Orderable.cs b/src/OrchardVNext/Data/Orderable.cs new file mode 100644 index 00000000000..d3c6066f408 --- /dev/null +++ b/src/OrchardVNext/Data/Orderable.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace OrchardVNext.Data { + public class Orderable { + private IQueryable _queryable; + + public Orderable(IQueryable enumerable) { + _queryable = enumerable; + } + + public IQueryable Queryable { + get { return _queryable; } + } + + public Orderable Asc(Expression> keySelector) { + _queryable = _queryable + .OrderBy(keySelector); + return this; + } + + public Orderable Asc(Expression> keySelector1, + Expression> keySelector2) { + _queryable = _queryable + .OrderBy(keySelector1) + .ThenBy(keySelector2); + return this; + } + + public Orderable Asc(Expression> keySelector1, + Expression> keySelector2, + Expression> keySelector3) { + _queryable = _queryable + .OrderBy(keySelector1) + .ThenBy(keySelector2) + .ThenBy(keySelector3); + return this; + } + + public Orderable Desc(Expression> keySelector) { + _queryable = _queryable + .OrderByDescending(keySelector); + return this; + } + + public Orderable Desc(Expression> keySelector1, + Expression> keySelector2) { + _queryable = _queryable + .OrderByDescending(keySelector1) + .ThenByDescending(keySelector2); + return this; + } + + public Orderable Desc(Expression> keySelector1, + Expression> keySelector2, + Expression> keySelector3) { + _queryable = _queryable + .OrderByDescending(keySelector1) + .ThenByDescending(keySelector2) + .ThenByDescending(keySelector3); + return this; + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/PersistentAttribute.cs b/src/OrchardVNext/Data/PersistentAttribute.cs new file mode 100644 index 00000000000..760eaab759e --- /dev/null +++ b/src/OrchardVNext/Data/PersistentAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace OrchardVNext.Data { + [AttributeUsage(AttributeTargets.Class)] + public class PersistentAttribute : Attribute { + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/Providers/DataServiceParameters.cs b/src/OrchardVNext/Data/Providers/DataServiceParameters.cs new file mode 100644 index 00000000000..2af42f298f7 --- /dev/null +++ b/src/OrchardVNext/Data/Providers/DataServiceParameters.cs @@ -0,0 +1,7 @@ +namespace OrchardVNext.Data.Providers { + public class DataServiceParameters { + public string Provider { get; set; } + public string DataFolder { get; set; } + public string ConnectionString { get; set; } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/Providers/IDataServicesProvider.cs b/src/OrchardVNext/Data/Providers/IDataServicesProvider.cs new file mode 100644 index 00000000000..fdcc2838977 --- /dev/null +++ b/src/OrchardVNext/Data/Providers/IDataServicesProvider.cs @@ -0,0 +1,28 @@ +using Microsoft.Data.Entity; + +namespace OrchardVNext.Data.Providers { + public interface IDataServicesProvider : ITransientDependency { + string ProviderName { get; } + void ConfigureContextOptions(DbContextOptionsBuilder optionsBuilders); + } + + public class SqlServerDataServicesProvider : IDataServicesProvider { + public string ProviderName { + get { return "SqlServer"; } + } + + public void ConfigureContextOptions(DbContextOptionsBuilder optionsBuilders) { + optionsBuilders.UseSqlServer(@""); + } + } + + public class InMemoryDataServicesProvider : IDataServicesProvider { + public string ProviderName { + get { return "InMemory"; } + } + + public void ConfigureContextOptions(DbContextOptionsBuilder optionsBuilders) { + optionsBuilders.UseInMemoryStore(persist: true); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Data/Providers/IDataServicesProviderFactory.cs b/src/OrchardVNext/Data/Providers/IDataServicesProviderFactory.cs new file mode 100644 index 00000000000..829a3243dd7 --- /dev/null +++ b/src/OrchardVNext/Data/Providers/IDataServicesProviderFactory.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OrchardVNext.Data.Providers { + public interface IDataServicesProviderFactory : IDependency { + IDataServicesProvider CreateProvider(DataServiceParameters sessionFactoryParameters); + } + + public class DataServicesProviderFactory : IDataServicesProviderFactory { + private readonly IEnumerable _dataServicesProviders; + public DataServicesProviderFactory(IEnumerable dataServicesProviders) { + _dataServicesProviders = dataServicesProviders; + } + + public IDataServicesProvider CreateProvider(DataServiceParameters sessionFactoryParameters) { + return _dataServicesProviders.First(x => x.ProviderName == sessionFactoryParameters.Provider); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Environment/Configuration/ShellSettings.cs b/src/OrchardVNext/Environment/Configuration/ShellSettings.cs index dc41204ac3c..e69ac8cfc6c 100644 --- a/src/OrchardVNext/Environment/Configuration/ShellSettings.cs +++ b/src/OrchardVNext/Environment/Configuration/ShellSettings.cs @@ -22,6 +22,10 @@ public ShellSettings(ShellSettings settings) { _values = new Dictionary(settings._values, StringComparer.OrdinalIgnoreCase); Name = settings.Name; + DataProvider = settings.DataProvider; + DataConnectionString = settings.DataConnectionString; + DataTablePrefix = settings.DataTablePrefix; + RequestUrlHost = settings.RequestUrlHost; RequestUrlPrefix = settings.RequestUrlPrefix; State = settings.State; } @@ -37,20 +41,58 @@ public string this[string key] { /// /// Gets all keys held by this shell settings. /// - public IEnumerable Keys { get { return _values.Keys; } } + public IEnumerable Keys => _values.Keys; /// /// The name of the tenant /// - public string Name { + public string Name + { get { return this["Name"] ?? ""; } set { this["Name"] = value; } } + /// + /// The database provider for the tenant + /// + public string DataProvider + { + get { return this["DataProvider"] ?? ""; } + set { this["DataProvider"] = value; } + } + + /// + /// The database connection string + /// + public string DataConnectionString + { + get { return this["DataConnectionString"]; } + set { this["DataConnectionString"] = value; } + } + + /// + /// The data table prefix added to table names for this tenant + /// + public string DataTablePrefix + { + get { return this["DataTablePrefix"]; } + set { this["DataTablePrefix"] = value; } + } + + /// + /// The host name of the tenant + /// + public string RequestUrlHost + { + get { return this["RequestUrlHost"]; } + set { this["RequestUrlHost"] = value; } + } + /// /// The request url prefix of the tenant /// - public string RequestUrlPrefix { + public string RequestUrlPrefix + { get { return this["RequestUrlPrefix"]; } set { _values["RequestUrlPrefix"] = value; } } diff --git a/src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs b/src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs index fd7de9e12ff..29e29b95031 100644 --- a/src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs +++ b/src/OrchardVNext/Environment/Configuration/ShellSettingsManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -35,7 +35,7 @@ IEnumerable IShellSettingsManager.LoadSettings() { List shellSettings = new List(); foreach (var filePath in filePaths) { - IConfigurationSourceContainer configurationContainer = null; + IConfigurationSourceRoot configurationContainer = null; var extension = Path.GetExtension(filePath); @@ -61,6 +61,10 @@ IEnumerable IShellSettingsManager.LoadSettings() { if (configurationContainer != null) { var shellSetting = new ShellSettings { Name = configurationContainer.Get("Name"), + DataConnectionString = configurationContainer.Get("DataConnectionString"), + DataProvider = configurationContainer.Get("DataProvider"), + DataTablePrefix = configurationContainer.Get("DataTablePrefix"), + RequestUrlHost = configurationContainer.Get("RequestUrlHost"), RequestUrlPrefix = configurationContainer.Get("RequestUrlPrefix") }; diff --git a/src/OrchardVNext/Environment/DefaultAssemblyLoader.cs b/src/OrchardVNext/Environment/DefaultAssemblyLoader.cs index d81daf95d00..0cfb7ea0538 100644 --- a/src/OrchardVNext/Environment/DefaultAssemblyLoader.cs +++ b/src/OrchardVNext/Environment/DefaultAssemblyLoader.cs @@ -2,6 +2,8 @@ using System.Reflection; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Caching; +using Microsoft.Framework.Runtime.Compilation; using Microsoft.Framework.Runtime.Loader; using Microsoft.Framework.Runtime.Roslyn; using OrchardVNext.Environment.Extensions.Loaders; @@ -15,9 +17,8 @@ public class ExtensionAssemblyLoader : IAssemblyLoader { private readonly IFileWatcher _fileWatcher; private readonly IOrchardLibraryManager _orchardLibraryManager; private readonly IAssemblyLoadContextAccessor _assemblyLoadContextAccessor; - private readonly IVirtualPathProvider _virtualPathProvider; - private readonly string _path; - + private readonly IVirtualPathProvider _virtualPathProvider;private readonly string _path; + public ExtensionAssemblyLoader(string path, IServiceProvider serviceProvider) { _path = path; _serviceProvider = serviceProvider; @@ -27,6 +28,7 @@ public ExtensionAssemblyLoader(string path, IServiceProvider serviceProvider) { _assemblyLoadContextAccessor = serviceProvider.GetService(); _virtualPathProvider = serviceProvider.GetService(); } + public Assembly Load(string name) { Project project; @@ -81,7 +83,8 @@ public Assembly Load(string name) { var compliationContext = compiler.CompileProject(project, target, exports.MetadataReferences, - exports.SourceReferences); + exports.SourceReferences, + () => CompositeResourceProvider.Default.GetResources(project)); var roslynProjectReference = new RoslynProjectReference(compliationContext); diff --git a/src/OrchardVNext/Environment/DefaultHostEnvironment.cs b/src/OrchardVNext/Environment/DefaultHostEnvironment.cs index 77c7daa2539..6abf929a7e8 100644 --- a/src/OrchardVNext/Environment/DefaultHostEnvironment.cs +++ b/src/OrchardVNext/Environment/DefaultHostEnvironment.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNet.Hosting; -using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime; using OrchardVNext.Localization; namespace OrchardVNext.Environment { diff --git a/src/OrchardVNext/Environment/Extensions/Folders/CoreModuleFolders.cs b/src/OrchardVNext/Environment/Extensions/Folders/CoreModuleFolders.cs index 3a9734be7c2..31530f4eb15 100644 --- a/src/OrchardVNext/Environment/Extensions/Folders/CoreModuleFolders.cs +++ b/src/OrchardVNext/Environment/Extensions/Folders/CoreModuleFolders.cs @@ -6,11 +6,11 @@ public class CoreModuleFolders : IExtensionFolders { private readonly IExtensionHarvester _extensionHarvester; public CoreModuleFolders(IExtensionHarvester extensionHarvester) { - SearchPaths = new[] { "~/Core" }; + SearchPaths = new[] { "~/Core/OrchardVNext.Core" }; _extensionHarvester = extensionHarvester; } - public string[] SearchPaths { get; private set; } + public string[] SearchPaths { get; } public IEnumerable AvailableExtensions() { return _extensionHarvester.HarvestExtensions(SearchPaths, DefaultExtensionTypes.Module, "Module.txt", false/*isManifestOptional*/); diff --git a/src/OrchardVNext/Environment/Extensions/Folders/ModuleFolders.cs b/src/OrchardVNext/Environment/Extensions/Folders/ModuleFolders.cs index 3ebd2b9c4df..6a411b17160 100644 --- a/src/OrchardVNext/Environment/Extensions/Folders/ModuleFolders.cs +++ b/src/OrchardVNext/Environment/Extensions/Folders/ModuleFolders.cs @@ -9,7 +9,9 @@ public ModuleFolders(IExtensionHarvester extensionHarvester) { SearchPaths = new [] { "~/Modules" }; _extensionHarvester = extensionHarvester; } - public string[] SearchPaths { get; private set; } + + public string[] SearchPaths { get; } + public IEnumerable AvailableExtensions() { return _extensionHarvester.HarvestExtensions(SearchPaths, DefaultExtensionTypes.Module, "Module.txt", false/*isManifestOptional*/); } diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/CoreExtensionLoader.cs b/src/OrchardVNext/Environment/Extensions/Loaders/CoreExtensionLoader.cs new file mode 100644 index 00000000000..ac7ae30001c --- /dev/null +++ b/src/OrchardVNext/Environment/Extensions/Loaders/CoreExtensionLoader.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Framework.Runtime; +using OrchardVNext.Environment.Extensions.Models; +using OrchardVNext.FileSystems.VirtualPath; + +namespace OrchardVNext.Environment.Extensions.Loaders { + public class CoreExtensionLoader : IExtensionLoader { + private const string CoreAssemblyName = "OrchardVNext.Core"; + private readonly IVirtualPathProvider _virtualPathProvider; + private readonly IServiceProvider _serviceProvider; + private readonly IAssemblyLoaderContainer _loaderContainer; + + public CoreExtensionLoader( + IVirtualPathProvider virtualPathProvider, + IServiceProvider serviceProvider, + IAssemblyLoaderContainer container) { + + _virtualPathProvider = virtualPathProvider; + _serviceProvider = serviceProvider; + _loaderContainer = container; + + } + + public string Name => GetType().Name; + + public int Order => 10; + + public void ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { + } + + public void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { + } + + public bool IsCompatibleWithModuleReferences(ExtensionDescriptor extension, IEnumerable references) { + return true; + } + + public ExtensionEntry Load(ExtensionDescriptor descriptor) { + + if (!descriptor.Location.StartsWith("~/Core/")) { + return null; + } + + var plocation = _virtualPathProvider.MapPath("~/Core"); + + using (_loaderContainer.AddLoader(new ExtensionAssemblyLoader(plocation, _serviceProvider))) { + var assembly = Assembly.Load(new AssemblyName(CoreAssemblyName)); + + Logger.Information("Loaded referenced extension \"{0}\": assembly name=\"{1}\"", descriptor.Name, assembly.FullName); + + return new ExtensionEntry { + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.ExportedTypes.Where(x => IsTypeFromModule(x, descriptor)) + }; + } + } + + public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { + return null; + } + + public void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { + } + + public void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { + } + private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) { + return (type.Namespace + ".").StartsWith(CoreAssemblyName + "." + descriptor.Id + "."); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/DefaultExtensionLoader.cs b/src/OrchardVNext/Environment/Extensions/Loaders/DynamicExtensionLoader.cs similarity index 87% rename from src/OrchardVNext/Environment/Extensions/Loaders/DefaultExtensionLoader.cs rename to src/OrchardVNext/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index a99f25f781a..e331f52f74d 100644 --- a/src/OrchardVNext/Environment/Extensions/Loaders/DefaultExtensionLoader.cs +++ b/src/OrchardVNext/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -2,17 +2,20 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.Versioning; +using System.Linq; using Microsoft.Framework.Runtime; using OrchardVNext.Environment.Extensions.Models; using OrchardVNext.FileSystems.VirtualPath; namespace OrchardVNext.Environment.Extensions.Loaders { - public class DefaultExtensionLoader : IExtensionLoader { + public class DynamicExtensionLoader : IExtensionLoader { + public static readonly string[] ExtensionsVirtualPathPrefixes = { "~/Modules", "~/Themes" }; + private readonly IVirtualPathProvider _virtualPathProvider; private readonly IServiceProvider _serviceProvider; private readonly IAssemblyLoaderContainer _loaderContainer; - public DefaultExtensionLoader( + public DynamicExtensionLoader( IVirtualPathProvider virtualPathProvider, IServiceProvider serviceProvider, IAssemblyLoaderContainer container) { @@ -25,7 +28,7 @@ public DefaultExtensionLoader( public string Name => GetType().Name; - public int Order => 1; + public int Order => 20; public void ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { } @@ -38,6 +41,9 @@ public bool IsCompatibleWithModuleReferences(ExtensionDescriptor extension, IEnu } public ExtensionEntry Load(ExtensionDescriptor descriptor) { + if (!ExtensionsVirtualPathPrefixes.Contains(descriptor.Location)) { + return null; + } var plocation = _virtualPathProvider.MapPath(descriptor.Location); @@ -46,7 +52,6 @@ public ExtensionEntry Load(ExtensionDescriptor descriptor) { Logger.Information("Loaded referenced extension \"{0}\": assembly name=\"{1}\"", descriptor.Name, assembly.FullName); - return new ExtensionEntry { Descriptor = descriptor, Assembly = assembly, diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/InternalRoslynCompiler.cs b/src/OrchardVNext/Environment/Extensions/Loaders/InternalRoslynCompiler.cs index bd5d2f96c43..e9c0c0fe206 100644 --- a/src/OrchardVNext/Environment/Extensions/Loaders/InternalRoslynCompiler.cs +++ b/src/OrchardVNext/Environment/Extensions/Loaders/InternalRoslynCompiler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -12,9 +12,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; -using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Runtime.Caching; using Microsoft.Framework.Runtime.Common.DependencyInjection; +using Microsoft.Framework.Runtime.Compilation; using OrchardVNext; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.Framework.Runtime.Roslyn { public class InternalRoslynCompiler { @@ -43,10 +45,11 @@ public InternalRoslynCompiler(ICache cache, } public CompilationContext CompileProject( - Project project, + ICompilationProject project, ILibraryKey target, IEnumerable incomingReferences, - IEnumerable incomingSourceReferences) { + IEnumerable incomingSourceReferences, + Func> resourcesResolver) { var path = project.ProjectDirectory; var name = project.Name.TrimStart('/'); @@ -70,6 +73,8 @@ public CompilationContext CompileProject( _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(buildOutputsName)); } + + _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(project.Name + "_Dependencies")); } var exportedReferences = incomingReferences.Select(ConvertMetadataReference); @@ -112,7 +117,21 @@ public CompilationContext CompileProject( compilation = ApplyVersionInfo(compilation, project, parseOptions); - var compilationContext = new CompilationContext(compilation, project, target.TargetFramework, target.Configuration); + var compilationContext = new CompilationContext( + compilation, + project, + target.TargetFramework, + target.Configuration, + incomingReferences, + () => resourcesResolver() + .Select(res => new ResourceDescription( + res.Name, + res.StreamFactory, + isPublic: true)) + .ToList()); + + // Apply strong-name settings + ApplyStrongNameSettings(compilationContext); if (isMainAspect && project.Files.PreprocessSourceFiles.Any()) { try { @@ -142,7 +161,7 @@ public CompilationContext CompileProject( if (compilationContext.Modules.Count > 0) { var precompSw = Stopwatch.StartNew(); foreach (var module in compilationContext.Modules) { - module.BeforeCompile(compilationContext); + module.BeforeCompile(compilationContext.BeforeCompileContext); } precompSw.Stop(); @@ -155,6 +174,31 @@ public CompilationContext CompileProject( return compilationContext; } + private void ApplyStrongNameSettings(CompilationContext compilationContext) { + // This is temporary, eventually we'll want a project.json feature for this + var keyFile = Environment.GetEnvironmentVariable(EnvironmentNames.BuildKeyFile); + if (!string.IsNullOrEmpty(keyFile)) { +#if DNX451 + var delaySignString = Environment.GetEnvironmentVariable(EnvironmentNames.BuildDelaySign); + var delaySign = !string.IsNullOrEmpty(delaySignString) && ( + string.Equals(delaySignString, "true", StringComparison.OrdinalIgnoreCase) || + string.Equals(delaySignString, "1", StringComparison.OrdinalIgnoreCase)); + + var strongNameProvider = new DesktopStrongNameProvider(); + var newOptions = compilationContext.Compilation.Options + .WithStrongNameProvider(strongNameProvider) + .WithCryptoKeyFile(keyFile) + .WithDelaySign(delaySign); + compilationContext.Compilation = compilationContext.Compilation.WithOptions(newOptions); +#else + var diag = Diagnostic.Create( + RoslynDiagnostics.StrongNamingNotSupported, + null); + compilationContext.Diagnostics.Add(diag); +#endif + } + } + private CompilationModules GetCompileModules(ILibraryKey target) { // The only thing that matters is the runtime environment // when loading the compilation modules, so use that as the cache key @@ -187,23 +231,69 @@ private CompilationModules GetCompileModules(ILibraryKey target) { }); } - private static CSharpCompilation ApplyVersionInfo(CSharpCompilation compilation, Project project, + private static CSharpCompilation ApplyVersionInfo(CSharpCompilation compilation, ICompilationProject project, CSharpParseOptions parseOptions) { - var emptyVersion = new Version(0, 0, 0, 0); + const string assemblyFileVersionName = "System.Reflection.AssemblyFileVersionAttribute"; + const string assemblyVersionName = "System.Reflection.AssemblyVersionAttribute"; + const string assemblyInformationalVersion = "System.Reflection.AssemblyInformationalVersionAttribute"; + + var assemblyAttributes = compilation.Assembly.GetAttributes(); + + var foundAssemblyFileVersion = false; + var foundAssemblyVersion = false; + var foundAssemblyInformationalVersion = false; + + foreach (var assembly in assemblyAttributes) { + string attributeName = assembly.AttributeClass.ToString(); + + if (string.Equals(attributeName, assemblyFileVersionName, StringComparison.Ordinal)) { + foundAssemblyFileVersion = true; + } + else if (string.Equals(attributeName, assemblyVersionName, StringComparison.Ordinal)) { + foundAssemblyVersion = true; + } + else if (string.Equals(attributeName, assemblyInformationalVersion, StringComparison.Ordinal)) { + foundAssemblyInformationalVersion = true; + } + } + + var versionAttributes = new StringBuilder(); + if (!foundAssemblyFileVersion) { + versionAttributes.AppendLine($"[assembly:{assemblyFileVersionName}(\"{project.AssemblyFileVersion}\")]"); + } - // If the assembly version is empty then set the version - if (compilation.Assembly.Identity.Version == emptyVersion) { - return compilation.AddSyntaxTrees(new[] + if (!foundAssemblyVersion) { + versionAttributes.AppendLine($"[assembly:{assemblyVersionName}(\"{RemovePrereleaseTag(project.Version)}\")]"); + } + + if (!foundAssemblyInformationalVersion) { + versionAttributes.AppendLine($"[assembly:{assemblyInformationalVersion}(\"{project.Version}\")]"); + } + + if (versionAttributes.Length != 0) { + compilation = compilation.AddSyntaxTrees(new[] { - CSharpSyntaxTree.ParseText("[assembly: System.Reflection.AssemblyVersion(\"" + project.Version.Version + "\")]", parseOptions), - CSharpSyntaxTree.ParseText("[assembly: System.Reflection.AssemblyInformationalVersion(\"" + project.Version + "\")]", parseOptions) + CSharpSyntaxTree.ParseText(versionAttributes.ToString(), parseOptions) }); } return compilation; } - private IList GetSyntaxTrees(Project project, + private static string RemovePrereleaseTag(string version) { + // Simple reparse of the version string (because we don't want to pull in NuGet stuff + // here because we're in an old-runtime/new-runtime limbo) + + var dashIdx = version.IndexOf('-'); + if (dashIdx < 0) { + return version; + } + else { + return version.Substring(0, dashIdx); + } + } + + private IList GetSyntaxTrees(ICompilationProject project, IEnumerable sourceFiles, IEnumerable sourceReferences, CSharpParseOptions parseOptions, @@ -238,8 +328,7 @@ private IList GetSyntaxTrees(Project project, var ctx = _cacheContextAccessor.Current; foreach (var d in dirs) { - if (ctx != null) - ctx.Monitor(new FileWriteTimeCacheDependency(d)); + ctx?.Monitor(new FileWriteTimeCacheDependency(d)); // TODO: Make the file watcher hand out cache dependencies as well _watcher.WatchDirectory(d, ".cs"); @@ -308,11 +397,56 @@ private MetadataReference GetMetadataReference(string path) { private class CompilationModules : IDisposable { public IAssemblyLoadContext LoadContext { get; set; } + public List Modules { get; set; } public void Dispose() { LoadContext.Dispose(); } } + + static class Constants { + public const string BootstrapperExeName = "dnx"; + public const string BootstrapperFullName = "Microsoft .NET Execution environment"; + public const string DefaultLocalRuntimeHomeDir = ".dnx"; + public const string RuntimeShortName = "dnx"; + public const string RuntimeNamePrefix = RuntimeShortName + "-"; + public const string WebConfigRuntimeVersion = RuntimeNamePrefix + "version"; + public const string WebConfigRuntimeFlavor = RuntimeNamePrefix + "clr"; + public const string WebConfigRuntimeAppBase = RuntimeNamePrefix + "app-base"; + public const string WebConfigBootstrapperVersion = "bootstrapper-version"; + public const string WebConfigRuntimePath = "runtime-path"; + public const string BootstrapperHostName = RuntimeShortName + ".host"; + public const string BootstrapperClrName = RuntimeShortName + ".clr"; + public const string BootstrapperCoreclrManagedName = RuntimeShortName + ".coreclr.managed"; + } + + static class EnvironmentNames { + public static readonly string CommonPrefix = Constants.RuntimeShortName.ToUpper() + "_"; + public static readonly string Packages = CommonPrefix + "PACKAGES"; + public static readonly string PackagesCache = CommonPrefix + "PACKAGES_CACHE"; + public static readonly string Servicing = CommonPrefix + "SERVICING"; + public static readonly string Trace = CommonPrefix + "TRACE"; + public static readonly string CompilationServerPort = CommonPrefix + "COMPILATION_SERVER_PORT"; + public static readonly string Home = CommonPrefix + "HOME"; + public static readonly string GlobalPath = CommonPrefix + "GLOBAL_PATH"; + public static readonly string AppBase = CommonPrefix + "APPBASE"; + public static readonly string Framework = CommonPrefix + "FRAMEWORK"; + public static readonly string Configuration = CommonPrefix + "CONFIGURATION"; + public static readonly string ConsoleHost = CommonPrefix + "CONSOLE_HOST"; + public static readonly string DefaultLib = CommonPrefix + "DEFAULT_LIB"; + public static readonly string BuildKeyFile = CommonPrefix + "BUILD_KEY_FILE"; + public static readonly string BuildDelaySign = CommonPrefix + "BUILD_DELAY_SIGN"; + } + } + + internal class RoslynDiagnostics { + internal static readonly DiagnosticDescriptor StrongNamingNotSupported = new DiagnosticDescriptor( + id: "DNX1001", + title: "Strong name generation is not supported on this platform", + messageFormat: "Strong name generation is not supported on CoreCLR. Skipping strong name generation.", + category: "StrongNaming", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } -} +} \ No newline at end of file diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/ModuleLoaderContext.cs b/src/OrchardVNext/Environment/Extensions/Loaders/ModuleLoaderContext.cs index cf0302e680a..72f8c746a9b 100644 --- a/src/OrchardVNext/Environment/Extensions/Loaders/ModuleLoaderContext.cs +++ b/src/OrchardVNext/Environment/Extensions/Loaders/ModuleLoaderContext.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Compilation; using Microsoft.Framework.Runtime.DependencyManagement; using NuGet; diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/ModuleProjectLibraryExportProvider.cs b/src/OrchardVNext/Environment/Extensions/Loaders/ModuleProjectLibraryExportProvider.cs index 8d56a3e1aac..0d68d5f2577 100644 --- a/src/OrchardVNext/Environment/Extensions/Loaders/ModuleProjectLibraryExportProvider.cs +++ b/src/OrchardVNext/Environment/Extensions/Loaders/ModuleProjectLibraryExportProvider.cs @@ -2,11 +2,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.Versioning; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Runtime.Compilation; +using System.Linq; -namespace OrchardVNext.Environment.Extensions.Loaders -{ +namespace OrchardVNext.Environment.Extensions.Loaders { public class ModuleProjectLibraryExportProvider : ILibraryExportProvider { private readonly IProjectResolver _projectResolver; private readonly IServiceProvider _serviceProvider; @@ -95,4 +97,102 @@ public static ILibraryKey ChangeAspect(this ILibraryKey target, string aspect) { }; } } + + internal class LibraryExport : ILibraryExport { + public LibraryExport(IList metadataReferences, IList sourceReferences) { + MetadataReferences = metadataReferences ?? new List(); + SourceReferences = sourceReferences ?? new List(); + } + + public IList MetadataReferences { get; } + public IList SourceReferences { get; } + } + + internal class CompiledProjectMetadataReference : IMetadataProjectReference, IMetadataFileReference { + private readonly ICompilationProject _project; + private readonly string _assemblyPath; + private readonly string _pdbPath; + + public CompiledProjectMetadataReference(ICompilationProject project, string assemblyPath, string pdbPath) { + Name = project.Name; + ProjectPath = project.ProjectFilePath; + Path = assemblyPath; + + _project = project; + _assemblyPath = assemblyPath; + _pdbPath = pdbPath; + } + + public string Name { get; private set; } + + public string ProjectPath { get; private set; } + + public string Path { get; private set; } + + public IDiagnosticResult GetDiagnostics() { + return DiagnosticResult.Successful; + } + + public IList GetSources() { + return _project.Files.SourceFiles.Select(p => (ISourceReference)new SourceFileReference(p)) + .ToList(); + } + + public Assembly Load(IAssemblyLoadContext loadContext) { + return loadContext.LoadFile(_assemblyPath); + } + + public void EmitReferenceAssembly(Stream stream) { + using (var fs = File.OpenRead(_assemblyPath)) { + fs.CopyTo(stream); + } + } + + public IDiagnosticResult EmitAssembly(string outputPath) { + Copy(_assemblyPath, outputPath); + Copy(_pdbPath, outputPath); + + return DiagnosticResult.Successful; + } + + private static void Copy(string sourcePath, string outputPath) { + if (string.IsNullOrEmpty(sourcePath)) { + return; + } + + if (!File.Exists(sourcePath)) { + return; + } + + Directory.CreateDirectory(outputPath); + + File.Copy(sourcePath, System.IO.Path.Combine(outputPath, System.IO.Path.GetFileName(sourcePath)), overwrite: true); + } + } + + internal struct DiagnosticResult : IDiagnosticResult { + public static readonly DiagnosticResult Successful = new DiagnosticResult(success: true, + diagnostics: Enumerable.Empty()); + + public DiagnosticResult(bool success, IEnumerable diagnostics) { + Success = success; + Diagnostics = diagnostics.ToList(); + } + + public bool Success { get; } + + public IEnumerable Diagnostics { get; } + } + + internal class SourceFileReference : ISourceFileReference { + public SourceFileReference(string path) { + // Unique name of the reference + Name = path; + Path = path; + } + + public string Name { get; private set; } + + public string Path { get; private set; } + } } \ No newline at end of file diff --git a/src/OrchardVNext/Environment/Extensions/Loaders/OrchardLibraryManager.cs b/src/OrchardVNext/Environment/Extensions/Loaders/OrchardLibraryManager.cs index 2e7ef305c5d..792fba11622 100644 --- a/src/OrchardVNext/Environment/Extensions/Loaders/OrchardLibraryManager.cs +++ b/src/OrchardVNext/Environment/Extensions/Loaders/OrchardLibraryManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Compilation; namespace OrchardVNext.Environment.Extensions.Loaders { public interface IOrchardLibraryManager : ILibraryManager { diff --git a/src/OrchardVNext/Environment/OrchardStarter.cs b/src/OrchardVNext/Environment/OrchardStarter.cs index ea07651ad2a..a11a0d37b72 100644 --- a/src/OrchardVNext/Environment/OrchardStarter.cs +++ b/src/OrchardVNext/Environment/OrchardStarter.cs @@ -1,12 +1,12 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Hosting; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.ServiceLookup; using Microsoft.Framework.Logging; using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Caching; using OrchardVNext.Environment.Configuration; using OrchardVNext.Environment.Extensions; using OrchardVNext.Environment.Extensions.Folders; @@ -16,64 +16,60 @@ using OrchardVNext.FileSystems.VirtualPath; using OrchardVNext.FileSystems.WebSite; using OrchardVNext.Routing; -using System.Reflection; namespace OrchardVNext.Environment { public class OrchardStarter { - private static void CreateHostContainer(IApplicationBuilder app) { - app.UseServices(services => { - services.AddSingleton(); - services.AddSingleton(); + public static void ConfigureHost(IServiceCollection services) { + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); + // Caching - Move out + services.AddInstance(new CacheContextAccessor()); + services.AddSingleton(); - // Caching - Move out? - services.AddInstance(new CacheContextAccessor()); - services.AddSingleton(); + services.AddTransient(); + { + services.AddSingleton(); - services.AddTransient(); + services.AddSingleton(); { - services.AddSingleton(); - - services.AddSingleton(); + services.AddSingleton(); { - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); { - services.AddSingleton(); - services.AddSingleton(); - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - } + services.AddSingleton(); + services.AddSingleton(); } - - services.AddSingleton(); } + + services.AddSingleton(); } + } - services.AddTransient(); + services.AddTransient(); - services.AddInstance(new ServiceManifest(services)); - }); - + services.AddInstance(new ServiceManifest(services)); + } + + public static IOrchardHost CreateHost(IApplicationBuilder app, ILoggerFactory loggerFactory) { + loggerFactory.AddProvider(new TestLoggerProvider()); + app.UseMiddleware(); app.UseMiddleware(); // Think this needs to be inserted in a different part of the pipeline, possibly // dhen DI is created for the shell app.UseMiddleware(); - } - - public static IOrchardHost CreateHost(IApplicationBuilder app) { - CreateHostContainer(app); - + return app.ApplicationServices.GetService(); } } diff --git a/src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs b/src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs index e0fe49c1023..a4ee74f9a96 100644 --- a/src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs +++ b/src/OrchardVNext/Environment/ShellBuilders/CompositionStrategy.cs @@ -9,11 +9,13 @@ using System.Linq; using System.Reflection; -namespace OrchardVNext.Environment.ShellBuilders { +namespace OrchardVNext.Environment.ShellBuilders +{ /// /// Service at the host level to transform the cachable descriptor into the loadable blueprint. /// - public interface ICompositionStrategy { + public interface ICompositionStrategy + { /// /// Using information from the IExtensionManager, transforms and populates all of the /// blueprint model the shell builders will need to correctly initialize a tenant IoC container. diff --git a/src/OrchardVNext/Environment/ShellBuilders/ShellContainerFactory.cs b/src/OrchardVNext/Environment/ShellBuilders/ShellContainerFactory.cs index 06df9adb05f..d399ee0d2a8 100644 --- a/src/OrchardVNext/Environment/ShellBuilders/ShellContainerFactory.cs +++ b/src/OrchardVNext/Environment/ShellBuilders/ShellContainerFactory.cs @@ -1,10 +1,7 @@ -using Microsoft.AspNet.Hosting; -using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.DependencyInjection.ServiceLookup; using Microsoft.Framework.Runtime; using OrchardVNext.Environment.Configuration; using OrchardVNext.Environment.Extensions.Loaders; @@ -15,20 +12,25 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNet.Hosting; +using Microsoft.Framework.Logging; +using OrchardVNext.Data.EF; namespace OrchardVNext.Environment.ShellBuilders { public interface IShellContainerFactory { IServiceProvider CreateContainer(ShellSettings settings, ShellBlueprint blueprint); } - public class ShellContainerFactory : IShellContainerFactory { + public class ShellContainerFactory : IShellContainerFactory + { private readonly IServiceProvider _serviceProvider; public ShellContainerFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } - public IServiceProvider CreateContainer(ShellSettings settings, ShellBlueprint blueprint) { + public IServiceProvider CreateContainer(ShellSettings settings, ShellBlueprint blueprint) + { ServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddScoped(); @@ -37,9 +39,14 @@ public IServiceProvider CreateContainer(ShellSettings settings, ShellBlueprint b serviceCollection.AddInstance(blueprint.Descriptor); serviceCollection.AddInstance(blueprint); + serviceCollection.AddEntityFramework() + .AddInMemoryStore() + .AddDbContext(); + serviceCollection.AddMvc(); - serviceCollection.Configure(options => { + serviceCollection.Configure(options => + { var expander = new ModuleViewLocationExpander(); options.ViewLocationExpanders.Add(expander); }); @@ -47,62 +54,74 @@ public IServiceProvider CreateContainer(ShellSettings settings, ShellBlueprint b var p = _serviceProvider.GetService(); serviceCollection.AddInstance(new DefaultAssemblyProviderTest(p, _serviceProvider, _serviceProvider.GetService())); - foreach (var dependency in blueprint.Dependencies) { + foreach (var dependency in blueprint.Dependencies) + { foreach (var interfaceType in dependency.Type.GetInterfaces() - .Where(itf => typeof(IDependency).IsAssignableFrom(itf))) { + .Where(itf => typeof(IDependency).IsAssignableFrom(itf))) + { Logger.Debug("Type: {0}, Interface Type: {1}", dependency.Type, interfaceType); - if (typeof(ISingletonDependency).IsAssignableFrom(interfaceType)) { + if (typeof(ISingletonDependency).IsAssignableFrom(interfaceType)) + { serviceCollection.AddSingleton(interfaceType, dependency.Type); } - else if (typeof(IUnitOfWorkDependency).IsAssignableFrom(interfaceType)) { + else if (typeof(IUnitOfWorkDependency).IsAssignableFrom(interfaceType)) + { serviceCollection.AddScoped(interfaceType, dependency.Type); } - else if (typeof (ITransientDependency).IsAssignableFrom(interfaceType)) { + else if (typeof(ITransientDependency).IsAssignableFrom(interfaceType)) + { serviceCollection.AddTransient(interfaceType, dependency.Type); } - else { + else + { serviceCollection.AddScoped(interfaceType, dependency.Type); } } } - //foreach (var item in blueprint.Controllers) { - // var serviceKeyName = (item.AreaName + "/" + item.ControllerName).ToLowerInvariant(); - // var serviceKeyType = item.Type; - // serviceCollection.AddScoped(serviceKeyType); - - //} + serviceCollection.AddLogging(); - return BuildFallbackServiceProvider( - serviceCollection, - _serviceProvider); + return new WrappingServiceProvider(_serviceProvider, serviceCollection); } + private class WrappingServiceProvider : IServiceProvider + { + private readonly IServiceProvider _services; + + // Need full wrap for generics like IOptions + public WrappingServiceProvider(IServiceProvider fallback, IServiceCollection replacedServices) + { + var services = new ServiceCollection(); + var manifest = fallback.GetRequiredService(); + foreach (var service in manifest.Services) { + services.AddTransient(service, sp => fallback.GetService(service)); + } + + services.AddSingleton(sp => new HostingManifest(services)); + services.Add(replacedServices); - private static IServiceProvider BuildFallbackServiceProvider(IEnumerable services, - IServiceProvider fallback) { - var sc = HostingServices.Create(fallback); - sc.Add(services); + _services = services.BuildServiceProvider(); + } + + public object GetService(Type serviceType) { + return _services.GetService(serviceType); + } - // Build the manifest - var manifestTypes = services.Where(t => t.ServiceType.GetTypeInfo().GenericTypeParameters.Length == 0 - && t.ServiceType != typeof(IServiceManifest) - && t.ServiceType != typeof(IServiceProvider)) - .Select(t => t.ServiceType).Distinct(); - sc.AddInstance(new ServiceManifest(manifestTypes, fallback.GetRequiredService())); - return sc.BuildServiceProvider(); - } - private class ServiceManifest : IServiceManifest { - public ServiceManifest(IEnumerable services, IServiceManifest fallback = null) { - Services = services; - if (fallback != null) { - Services = Services.Concat(fallback.Services).Distinct(); + // Manifest exposes the fallback manifest in addition to ITypeActivator, IHostingEnvironment, and ILoggerFactory + private class HostingManifest : IServiceManifest { + public HostingManifest(IServiceCollection hostServices) { + Services = new Type[] { + typeof(IHostingEnvironment), + typeof(ILoggerFactory), + typeof(IHttpContextAccessor), + typeof(IApplicationLifetime) + }.Concat(hostServices.Select(s => s.ServiceType)).Distinct(); } - } - public IEnumerable Services { get; private set; } + public IEnumerable Services { get; private set; } + } } } } \ No newline at end of file diff --git a/src/OrchardVNext/Environment/ShellBuilders/ShellContextFactory.cs b/src/OrchardVNext/Environment/ShellBuilders/ShellContextFactory.cs index 590ca32a212..f2d5edabce3 100644 --- a/src/OrchardVNext/Environment/ShellBuilders/ShellContextFactory.cs +++ b/src/OrchardVNext/Environment/ShellBuilders/ShellContextFactory.cs @@ -53,6 +53,7 @@ private static ShellDescriptor MinimumShellDescriptor() { SerialNumber = -1, Features = new[] { new ShellFeature {Name = "OrchardVNext.Framework"}, + new ShellFeature {Name = "Settings"}, new ShellFeature {Name = "OrchardVNext.Test1"}, new ShellFeature {Name = "OrchardVNext.Demo" } }, diff --git a/src/OrchardVNext/FakeLogger.cs b/src/OrchardVNext/FakeLogger.cs index 272d77cfa61..f63d2dfebcf 100644 --- a/src/OrchardVNext/FakeLogger.cs +++ b/src/OrchardVNext/FakeLogger.cs @@ -1,7 +1,7 @@ -using Microsoft.Framework.Logging; -using OrchardVNext.Logging; using System; using System.Linq; +using Microsoft.Framework.Logging; +using LogLevel = OrchardVNext.Logging.LogLevel; namespace OrchardVNext { public static class Logger { @@ -24,6 +24,7 @@ public static void Information(Exception e, string value, params object[] args) public static void Warning(string value, params object[] args) { Console.WriteLine(value, args); } + public static void Warning(Exception e, string value, params object[] args) { Console.WriteLine(value, args); Console.Error.WriteLine(e.ToString()); @@ -45,24 +46,20 @@ public static void TraceInformation(string message, params object[] args) { Console.WriteLine("Information: " + message, args); } - public static void TraceWarning(string message, params object[] args) { - Console.WriteLine("Warning: " + message, args); + public static void TraceWarning(string message, params object[] args) + { + Console.WriteLine("Warning: " + message, args); } - public static bool IsEnabled(OrchardVNext.Logging.LogLevel x) { + public static bool IsEnabled(LogLevel x) { return true; } } - public class TestLoggerFactory : ILoggerFactory { - public ILogger Create(string name) { + public class TestLoggerProvider : ILoggerProvider { + public ILogger CreateLogger(string name) { return new TestLogger(name, true); } - - public void AddProvider(ILoggerProvider provider) { - } - - public Microsoft.Framework.Logging.LogLevel MinimumLevel { get; set; } } public class TestLogger : ILogger { @@ -77,22 +74,27 @@ public TestLogger(string name, bool enabled) { public string Name { get; set; } - public IDisposable BeginScope(object state) { - _scope = state; - + public void Log(Microsoft.Framework.Logging.LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) + { + Logger.Information( + string.Format("LogLevel: {0}, EventId: {1}, State: {2}, Exception: {3}, Formatter: {4}, LoggerName: {5}, Scope: {6}", + logLevel, eventId, state, exception, formatter, _name, _scope + )); + } - return NullDisposable.Instance; + public bool IsEnabled(Microsoft.Framework.Logging.LogLevel logLevel) { + return _enabled; } - public void Write(Microsoft.Framework.Logging.LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) { - Logger.Information( - string.Format("LogLevel: {0}, EventId: {1}, State: {2}, Exception: {3}, Formatter: {4}, LoggerName: {5}, Scope: {6}", - logLevel, eventId, state, exception, formatter, _name, _scope - )); + public IDisposable BeginScopeImpl(object state) { + _scope = state; + + return new TestDisposable(); } - public bool IsEnabled(Microsoft.Framework.Logging.LogLevel logLevel) { - return _enabled; + private class TestDisposable : IDisposable { + public void Dispose() { + } } } } diff --git a/src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs b/src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs index 8145e0c1aa0..6b7124dbde4 100644 --- a/src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs +++ b/src/OrchardVNext/FileSystems/WebSite/WebSiteFolder.cs @@ -90,7 +90,7 @@ public void CopyFileTo(string virtualPath, Stream destination, bool actualConten } string Normalize(string virtualPath) { - return virtualPath.Replace(_hostingEnvironment.WebRoot, "~").Replace('\\', '/'); + return virtualPath.Replace(_hostingEnvironment.WebRootPath, "~").Replace('\\', '/'); } } } \ No newline at end of file diff --git a/src/OrchardVNext/InvokeExtensions.cs b/src/OrchardVNext/InvokeExtensions.cs new file mode 100644 index 00000000000..b1c2bb0aef7 --- /dev/null +++ b/src/OrchardVNext/InvokeExtensions.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using OrchardVNext.Security; +using OrchardVNext.Logging; +using OrchardVNext.Exceptions; + +namespace OrchardVNext { + + public static class InvokeExtensions { + + /// + /// Safely invoke methods by catching non fatal exceptions and logging them + /// + public static void Invoke(this IEnumerable events, Action dispatch) { + foreach (var sink in events) { + try { + dispatch(sink); + } + catch (Exception ex) { + if (IsLogged(ex)) { + Logger.Error(ex, "{2} thrown from {0} by {1}", + typeof(TEvents).Name, + sink.GetType().FullName, + ex.GetType().Name); + } + + if (ex.IsFatal()) { + throw; + } + } + } + } + + public static IEnumerable Invoke(this IEnumerable events, Func dispatch) { + + foreach (var sink in events) { + TResult result = default(TResult); + try { + result = dispatch(sink); + } + catch (Exception ex) { + if (IsLogged(ex)) { + Logger.Error(ex, "{2} thrown from {0} by {1}", + typeof(TEvents).Name, + sink.GetType().FullName, + ex.GetType().Name); + } + + if (ex.IsFatal()) { + throw; + } + } + + yield return result; + } + } + + + private static bool IsLogged(Exception ex) { + return ex is OrchardSecurityException || !ex.IsFatal(); + } + } +} diff --git a/src/OrchardVNext/Mvc/DefaultAssemblyProvider.cs b/src/OrchardVNext/Mvc/DefaultAssemblyProvider.cs index 07cc3f1ed15..a9dfe713139 100644 --- a/src/OrchardVNext/Mvc/DefaultAssemblyProvider.cs +++ b/src/OrchardVNext/Mvc/DefaultAssemblyProvider.cs @@ -14,15 +14,22 @@ public class DefaultAssemblyProviderTest : IAssemblyProvider { /// Gets the set of assembly names that are used as root for discovery of /// MVC controllers, view components and views. /// + // DefaultControllerTypeProvider uses CandidateAssemblies to determine if the base type of a POCO controller + // lives in an assembly that references MVC. CandidateAssemblies excludes all assemblies from the + // ReferenceAssemblies set. Consequently adding WebApiCompatShim to this set would cause the ApiController to + // fail this test. protected virtual HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.Ordinal) { "Microsoft.AspNet.Mvc", + "Microsoft.AspNet.Mvc.Abstractions", + "Microsoft.AspNet.Mvc.ApiExplorer", "Microsoft.AspNet.Mvc.Core", - "Microsoft.AspNet.Mvc.ModelBinding", "Microsoft.AspNet.Mvc.Razor", "Microsoft.AspNet.Mvc.Razor.Host", - "Microsoft.AspNet.Mvc.Rendering", + "Microsoft.AspNet.Mvc.TagHelpers", + "Microsoft.AspNet.Mvc.Xml", + "Microsoft.AspNet.PageExecutionInstrumentation.Interfaces", }; private readonly IOrchardLibraryManager _libraryManager; diff --git a/src/OrchardVNext/Mvc/Routes/DefaultRouteProvider.cs b/src/OrchardVNext/Mvc/Routes/DefaultRouteProvider.cs index c0d85e94e0f..56e8bb66e4e 100644 --- a/src/OrchardVNext/Mvc/Routes/DefaultRouteProvider.cs +++ b/src/OrchardVNext/Mvc/Routes/DefaultRouteProvider.cs @@ -1,4 +1,4 @@ -namespace Orchard.Mvc.Routes { +namespace OrchardVNext.Mvc.Routes { //public class DefaultRouteProvider : IRouteProvider { // public IEnumerable GetRoutes() { // return new[] { diff --git a/src/OrchardVNext/Mvc/Routes/DelegateRouteEndpoint.cs b/src/OrchardVNext/Mvc/Routes/DelegateRouteEndpoint.cs index 6f2fd0020dd..952e165b265 100644 --- a/src/OrchardVNext/Mvc/Routes/DelegateRouteEndpoint.cs +++ b/src/OrchardVNext/Mvc/Routes/DelegateRouteEndpoint.cs @@ -16,7 +16,7 @@ public async Task RouteAsync(RouteContext context) { context.IsHandled = true; } - public string GetVirtualPath(VirtualPathContext context) { + public VirtualPathData GetVirtualPath(VirtualPathContext context) { // We don't really care what the values look like. context.IsBound = true; return null; diff --git a/src/OrchardVNext/Mvc/Routes/TenantRoute.cs b/src/OrchardVNext/Mvc/Routes/TenantRoute.cs index 42874effa28..69db9ca279b 100644 --- a/src/OrchardVNext/Mvc/Routes/TenantRoute.cs +++ b/src/OrchardVNext/Mvc/Routes/TenantRoute.cs @@ -18,14 +18,20 @@ public TenantRoute(IRouter target, string urlHost, RequestDelegate pipeline) { public async Task RouteAsync(RouteContext context) { if (context.HttpContext.Request.Host.Value == _urlHost) { context.HttpContext.Items["orchard.Handler"] = new Func(async () => { - await _target.RouteAsync(context); + try { + await _target.RouteAsync(context); + } + catch (Exception e) { + Logger.Error(e, e.Message); + throw; + } }); await _pipeline.Invoke(context.HttpContext); } } - public string GetVirtualPath(VirtualPathContext context) { + public VirtualPathData GetVirtualPath(VirtualPathContext context) { return null; } } diff --git a/src/OrchardVNext/OrchardVNext.Framework.xproj b/src/OrchardVNext/OrchardVNext.Framework.xproj new file mode 100644 index 00000000000..02b8778ad61 --- /dev/null +++ b/src/OrchardVNext/OrchardVNext.Framework.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 60b5b61f-09c1-4f5c-8a5a-953574d0dca2 + Library + OrchardVNext + + + OrchardVNext.Framework + + + 2.0 + + + \ No newline at end of file diff --git a/src/OrchardVNext/Utility/ReflectionHelper.cs b/src/OrchardVNext/Utility/ReflectionHelper.cs new file mode 100644 index 00000000000..c667b423a58 --- /dev/null +++ b/src/OrchardVNext/Utility/ReflectionHelper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace OrchardVNext.Utility { + public class ReflectionHelper { + private static readonly ConcurrentDictionary _getterCache = + new ConcurrentDictionary(); + + public delegate TProperty PropertyGetterDelegate(T target); + + /// + /// Gets property info out of a Lambda. + /// + /// The return type of the Lambda. + /// The Lambda expression. + /// The property info. + public static PropertyInfo GetPropertyInfo(Expression> expression) { + var memberExpression = expression.Body as MemberExpression; + if (memberExpression == null) { + throw new InvalidOperationException("Expression is not a member expression."); + } + var propertyInfo = memberExpression.Member as PropertyInfo; + if (propertyInfo == null) { + throw new InvalidOperationException("Expression is not for a property."); + } + return propertyInfo; + } + + /// + /// Gets a delegate from a property expression. + /// + /// The type of the property. + /// The property expression. + /// The delegate. + public static PropertyGetterDelegate GetGetter( + Expression> targetExpression) { + + var propertyInfo = GetPropertyInfo(targetExpression); + return (PropertyGetterDelegate) _getterCache + .GetOrAdd(propertyInfo.Name, + s => propertyInfo.GetGetMethod().CreateDelegate(typeof(PropertyGetterDelegate))); + } + } +} \ No newline at end of file diff --git a/src/OrchardVNext/project.json b/src/OrchardVNext/project.json index 0657f55f9de..820c3abe17e 100644 --- a/src/OrchardVNext/project.json +++ b/src/OrchardVNext/project.json @@ -1,27 +1,27 @@ -{ - "version": "2.0.0-*", - "dependencies": { - "Microsoft.AspNet.Mvc": "6.0.0-beta4-*", - "Microsoft.AspNet.RequestContainer": "1.0.0-beta4-*", - "Microsoft.Framework.Runtime": "1.0.0-beta4-*", - "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-beta4-*", - "Microsoft.Framework.Runtime.Roslyn": "1.0.0-beta4-*", - "Microsoft.Framework.ConfigurationModel": "1.0.0-beta4-*", - "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4-*", - "Microsoft.Framework.ConfigurationModel.Xml": "1.0.0-beta4-*" - }, - "compilationOptions": { "define": [ "TRACE" ], "warningsAsErrors": true }, - "frameworks": { - "aspnet50": { - "dependencies": { - } - }, - "aspnetcore50": { - "dependencies": { - "System.Runtime": "4.0.20-beta-*", - "System.Xml.XDocument": "4.0.0-beta-*", - "System.IO.FileSystem.Primitives": "4.0.0-beta-*" - } - } - } +{ + "version": "2.0.0-*", + "dependencies": { + "Microsoft.AspNet.Mvc": "6.0.0-beta5-*", + "Microsoft.Framework.Runtime": "1.0.0-beta5-*", + "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-beta5-*", + "Microsoft.Framework.Runtime.Roslyn": "1.0.0-beta5-*", + "Microsoft.Framework.ConfigurationModel": "1.0.0-beta5-*", + "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta5-*", + "Microsoft.Framework.ConfigurationModel.Xml": "1.0.0-beta5-*", + "EntityFramework.Core": "7.0.0-beta5-*", + "EntityFramework.SqlServer": "7.0.0-beta5-*", + "EntityFramework.InMemory": "7.0.0-beta5-*", + "System.Console": "4.0.0-beta-*" + }, + "compilationOptions": { "define": [ "TRACE" ], "warningsAsErrors": true }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "System.Runtime": "4.0.20-beta-*", + "System.Xml.XDocument": "4.0.10-beta-*", + "System.IO.FileSystem.Primitives": "4.0.0-beta-*" + } + } + } } \ No newline at end of file