diff --git a/.hgignore b/.hgignore index 9474de352..2f0b5b1d9 100644 --- a/.hgignore +++ b/.hgignore @@ -7,6 +7,7 @@ glob:*.user glob:*.patch glob:*.hg glob:build +glob:artifacts glob:*.sln.cache glob:src/Orchard.Web/Media/* glob:desktop.ini diff --git a/AzurePackage.proj b/AzurePackage.proj new file mode 100644 index 000000000..ad02d137d --- /dev/null +++ b/AzurePackage.proj @@ -0,0 +1,137 @@ + + + + + + + $(MSBuildProjectDirectory)\lib + $(MSBuildProjectDirectory)\src + $(MSBuildProjectDirectory)\build + $(MSBuildProjectDirectory)\artifacts\Azure + + $(BuildFolder)\Compile + $(CompileFolder)\Orchard.Azure.CloudService.csx + $(ServiceFolder)\roles\Orchard.Azure.Web\approot + $(CompileFolder)\_PublishedWebsites + $(BuildFolder)\Stage + + x64 + x86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.cmd b/build.cmd index 8e320d6af..1e1be68bc 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,3 @@ if "%~1"=="" build Build -msbuild /t:%~1 +msbuild /t:%~1 Orchard.proj +msbuild /t:%~1 AzurePackage.proj diff --git a/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs b/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs index 405569136..ccd2a82cb 100644 --- a/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs +++ b/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs @@ -24,6 +24,12 @@ namespace Orchard.Azure.Tests.Environment.Configuration { DeleteAllBlobs( ((AzureShellSettingsManager)Loader).Container); } + [TearDown] + public void TearDown() { + // ensure default container is empty after running tests + DeleteAllBlobs(( (AzureShellSettingsManager)Loader ).Container); + } + [Test] public void SingleSettingsFileShouldComeBackAsExpected() { diff --git a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs index 5be4b3e8b..8c43b9a15 100644 --- a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs +++ b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs @@ -1,13 +1,9 @@ using System; -using System.Configuration; using System.IO; using NUnit.Framework; -using System.Diagnostics; using Orchard.Azure.Storage; using Microsoft.WindowsAzure; using System.Linq; -using Microsoft.WindowsAzure.StorageClient; -using System.Text; namespace Orchard.Azure.Tests.Storage { [TestFixture] diff --git a/src/Orchard.Azure/AzureFileSystem.cs b/src/Orchard.Azure/AzureFileSystem.cs new file mode 100644 index 000000000..4f66b1bc6 --- /dev/null +++ b/src/Orchard.Azure/AzureFileSystem.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.WindowsAzure; +using Microsoft.WindowsAzure.StorageClient; +using System.IO; +using Orchard.Storage; + +namespace Orchard.Azure { + public class AzureFileSystem { + public string ContainerName { get; protected set; } + + private readonly CloudStorageAccount _storageAccount; + private readonly string _shellName; + public CloudBlobClient BlobClient { get; private set; } + public CloudBlobContainer Container { get; private set; } + + public AzureFileSystem(string containerName, string shellName, bool isPrivate) + : this(containerName, shellName, isPrivate, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) { + } + + public AzureFileSystem(string containerName, string shellName, bool isPrivate, CloudStorageAccount storageAccount) { + // Setup the connection to custom storage accountm, e.g. Development Storage + _storageAccount = storageAccount; + ContainerName = containerName; + _shellName = shellName; + + BlobClient = _storageAccount.CreateCloudBlobClient(); + // Get and create the container if it does not exist + // The container is named with DNS naming restrictions (i.e. all lower case) + Container = BlobClient.GetContainerReference(ContainerName); + Container.CreateIfNotExist(); + + if ( isPrivate ) { + Container.SetPermissions(new BlobContainerPermissions + {PublicAccess = BlobContainerPublicAccessType.Off}); + } + else { + Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); + } + } + + private static void EnsurePathIsRelative(string path) { + if ( path.StartsWith("/") || path.StartsWith("http://")) + throw new ArgumentException("Path must be relative"); + } + + public IStorageFile GetFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + Container.EnsureBlobExists(path); + return new AzureBlobFileStorage(Container.GetBlockBlobReference(path)); + } + + public bool FileExists(string path) { + path = String.Concat(_shellName, "/", path); + return Container.BlobExists(path); + } + + public IEnumerable ListFiles(string path) { + EnsurePathIsRelative(path); + + string prefix = String.Concat(Container.Name, "/", _shellName, "/", path); + if ( !prefix.EndsWith("/") ) + prefix += "/"; + + foreach ( var blobItem in BlobClient.ListBlobsWithPrefix(prefix).OfType() ) { + yield return new AzureBlobFileStorage(blobItem); + } + } + + public IEnumerable ListFolders(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + if ( !Container.DirectoryExists(path) ) { + try { + CreateFolder(path); + } + catch ( Exception ex ) { + throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); + } + } + + return Container.GetDirectoryReference(path) + .ListBlobs() + .OfType() + .Select(d => new AzureBlobFolderStorage(d)) + .ToList(); + } + + public void CreateFolder(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureDirectoryDoesNotExist(path); + Container.GetDirectoryReference(path); + } + + public void DeleteFolder(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureDirectoryExists(path); + foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) { + if ( blob is CloudBlob ) + ( (CloudBlob)blob ).Delete(); + + if ( blob is CloudBlobDirectory ) + DeleteFolder(blob.Uri.ToString().Substring(Container.Uri.ToString().Length + 2 + _shellName.Length)); + } + } + + public void RenameFolder(string path, string newPath) { + EnsurePathIsRelative(path); + + EnsurePathIsRelative(newPath); + + if ( !path.EndsWith("/") ) + path += "/"; + + if ( !newPath.EndsWith("/") ) + newPath += "/"; + + foreach ( var blob in Container.GetDirectoryReference(_shellName + "/" + path).ListBlobs() ) { + if ( blob is CloudBlob ) { + string filename = Path.GetFileName(blob.Uri.ToString()); + string source = String.Concat(path, filename); + string destination = String.Concat(newPath, filename); + RenameFile(source, destination); + } + + if ( blob is CloudBlobDirectory ) { + string foldername = blob.Uri.Segments.Last(); + string source = String.Concat(path, foldername); + string destination = String.Concat(newPath, foldername); + RenameFolder(source, destination); + } + } + + } + + public void DeleteFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureBlobExists(path); + var blob = Container.GetBlockBlobReference(path); + blob.Delete(); + } + + public void RenameFile(string path, string newPath) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + EnsurePathIsRelative(newPath); + newPath = String.Concat(_shellName, "/", newPath); + + Container.EnsureBlobExists(path); + Container.EnsureBlobDoesNotExist(newPath); + + var blob = Container.GetBlockBlobReference(path); + var newBlob = Container.GetBlockBlobReference(newPath); + newBlob.CopyFromBlob(blob); + blob.Delete(); + } + + public IStorageFile CreateFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + if ( Container.BlobExists(path) ) { + throw new ArgumentException("File " + path + " already exists"); + } + + var blob = Container.GetBlockBlobReference(path); + blob.OpenWrite().Dispose(); // force file creation + return new AzureBlobFileStorage(blob); + } + + public string GetPublicUrl(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + Container.EnsureBlobExists(path); + return Container.GetBlockBlobReference(path).Uri.ToString(); + } + + private class AzureBlobFileStorage : IStorageFile { + private readonly CloudBlockBlob _blob; + + public AzureBlobFileStorage(CloudBlockBlob blob) { + _blob = blob; + } + + public string GetPath() { + return _blob.Uri.ToString(); + } + + public string GetName() { + return Path.GetFileName(GetPath()); + } + + public long GetSize() { + return _blob.Properties.Length; + } + + public DateTime GetLastUpdated() { + return _blob.Properties.LastModifiedUtc; + } + + public string GetFileType() { + return Path.GetExtension(GetPath()); + } + + public Stream OpenRead() { + return _blob.OpenRead(); + } + + public Stream OpenWrite() { + return _blob.OpenWrite(); + } + + } + + private class AzureBlobFolderStorage : IStorageFolder { + private readonly CloudBlobDirectory _blob; + + public AzureBlobFolderStorage(CloudBlobDirectory blob) { + _blob = blob; + } + + public string GetName() { + return Path.GetDirectoryName(_blob.Uri.ToString()); + } + + public string GetPath() { + return _blob.Uri.ToString(); + } + + public long GetSize() { + return GetDirectorySize(_blob); + } + + public DateTime GetLastUpdated() { + return DateTime.MinValue; + } + + public IStorageFolder GetParent() { + if ( _blob.Parent != null ) { + return new AzureBlobFolderStorage(_blob.Parent); + } + throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory"); + } + + private static long GetDirectorySize(CloudBlobDirectory directoryBlob) { + long size = 0; + + foreach ( var blobItem in directoryBlob.ListBlobs() ) { + if ( blobItem is CloudBlob ) + size += ( (CloudBlob)blobItem ).Properties.Length; + + if ( blobItem is CloudBlobDirectory ) + size += GetDirectorySize((CloudBlobDirectory)blobItem); + } + + return size; + } + } + + } +} diff --git a/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs b/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs new file mode 100644 index 000000000..e108a5f05 --- /dev/null +++ b/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Orchard.Environment.Configuration; + +namespace Orchard.Azure.Environment.Configuration { + public class AzureAppDataFolder : IAppDataFolder { + + private readonly AzureFileSystem _fs; + + public AzureAppDataFolder(string shellName) { + _fs = new AzureFileSystem("appdata", shellName, true); + } + + public void CreateFile(string path, string content) { + if(_fs.FileExists(path)) { + DeleteFile(path); + } + + using (var stream = _fs.CreateFile(path).OpenWrite()) { + using(var writer = new StreamWriter(stream)) { + writer.Write(content); + } + } + } + + public string ReadFile(string path) { + using ( var stream = _fs.GetFile(path).OpenRead() ) { + using ( var reader = new StreamReader(stream) ) { + return reader.ReadToEnd(); + } + } + } + + public void DeleteFile(string path) { + _fs.DeleteFile(path); + } + + public bool FileExists(string path) { + return _fs.FileExists(path); + } + + public IEnumerable ListFiles(string path) { + return _fs.ListFiles(path).Select(sf => sf.GetPath()); + } + + public IEnumerable ListDirectories(string path) { + return _fs.ListFolders(path).Select(sf => sf.GetName()); + } + + public string CreateDirectory(string path) { + _fs.CreateFolder(path); + return Path.GetDirectoryName(path); + } + + public void SetBasePath(string basePath) { + throw new NotImplementedException(); + } + + public string MapPath(string path) { + throw new NotImplementedException(); + } + } +} diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService.sln b/src/Orchard.Azure/Orchard.Azure.CloudService.sln new file mode 100644 index 000000000..8f2b2423a --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "Orchard.Azure.CloudService", "Orchard.Azure.CloudService\Orchard.Azure.CloudService.ccproj", "{03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.Web", "Orchard.Azure.Web\Orchard.Azure.Web.csproj", "{0DF8F426-9F30-4918-8F64-A5B40BA12D10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure", "Orchard.Azure.csproj", "{2505AA84-65A6-43D0-9C27-4F44FD576284}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Framework", "..\Orchard\Orchard.Framework.csproj", "{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Release|Any CPU.Build.0 = Release|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Release|Any CPU.Build.0 = Release|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Release|Any CPU.Build.0 = Release|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj b/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj new file mode 100644 index 000000000..22ee25d64 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj @@ -0,0 +1,51 @@ + + + + Debug + AnyCPU + 1.0.0 + {03c5327d-4e8e-45a7-acd1-e18e7caa3c4a} + Library + Properties + Orchard.Azure.CloudService + Orchard.Azure.CloudService + True + OrchardCloudService + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + Orchard.Azure.Web + {0df8f426-9f30-4918-8f64-a5b40ba12d10} + True + Web + Orchard.Azure.Web + + + + + $(MSBuildExtensionsPath)\Microsoft\Cloud Service\v1.0\ + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg new file mode 100644 index 000000000..7e0691f74 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef new file mode 100644 index 000000000..c1f66c33a --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config b/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config new file mode 100644 index 000000000..124536a7d --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config b/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config new file mode 100644 index 000000000..b56b3523b --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config @@ -0,0 +1,37 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Global.asax b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax new file mode 100644 index 000000000..8d7ecf448 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Orchard.Azure.Web.MvcApplication" Language="C#" %> diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs new file mode 100644 index 000000000..d7ee97dbf --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Autofac; +using Orchard.Environment; + +namespace Orchard.Azure.Web { + // Note: For instructions on enabling IIS6 or IIS7 classic mode, + // visit http://go.microsoft.com/?LinkId=9394801 + + public class MvcApplication : HttpApplication { + private static IOrchardHost _host; + + public static void RegisterRoutes(RouteCollection routes) { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + } + + protected void Application_Start() { + // This is temporary until MVC2 is officially released. + // We want to avoid running against an outdated preview installed in the GAC + CheckMvcVersion( + new Version("2.0.50217.0")/*MVC2 RTM file version #*/, + new Version("2.0.50129.0")/*MVC2 RC2 file version #*/, + new Version("2.0.41211.0")/*MVC2 RC file version #*/); + + RegisterRoutes(RouteTable.Routes); + + _host = OrchardStarter.CreateHost(MvcSingletons); + _host.Initialize(); + + //TODO: what's the failed initialization story - IoC failure in app start can leave you with a zombie appdomain + } + + protected void Application_BeginRequest() { + Context.Items["originalHttpContext"] = Context; + + _host.BeginRequest(); + } + + protected void Application_EndRequest() { + _host.EndRequest(); + } + + private void CheckMvcVersion(params Version[] allowedVersions) { + Assembly loadedMvcAssembly = typeof(System.Web.Mvc.Controller).Assembly; + Version loadedMvcVersion = ReadAssemblyFileVersion(loadedMvcAssembly); + + if ( allowedVersions.All(allowed => loadedMvcVersion != allowed) ) { + string message; + if ( loadedMvcAssembly.GlobalAssemblyCache ) { + message = string.Format( + "Orchard has been deployed with a version of {0} that has a different file version ({1}) " + + "than the version installed in the GAC ({2}).\r\n" + + "This implies that Orchard will not be able to run properly in this machine configuration.\r\n" + + "Please un-install MVC from the GAC or install a more recent version.", + loadedMvcAssembly.GetName().Name, + allowedVersions.First(), + loadedMvcVersion); + } + else { + message = string.Format( + "Orchard has been configured to run with a file version {1} of \"{0}\" " + + "but the version deployed with the application is {2}.\r\n" + + "This probably implies that Orchard is deployed with a newer version " + + "and the source code hasn't been updated accordingly.\r\n" + + "Update the Orchard.Web application source code (look for \"CheckMvcVersion\") to " + + "specify the correct file version number.\r\n", + loadedMvcAssembly.GetName().Name, + allowedVersions.First(), + loadedMvcVersion); + } + + throw new HttpException(500, message); + } + } + + private Version ReadAssemblyFileVersion(Assembly assembly) { + object[] attributes = assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true); + if ( attributes == null || attributes.Length != 1 ) { + string message = string.Format("Assembly \"{0}\" doesn't have a \"{1}\" attribute", + assembly.GetName().Name, typeof(AssemblyFileVersionAttribute).FullName); + throw new FileLoadException(message); + } + + var attribute = (AssemblyFileVersionAttribute)attributes[0]; + return new Version(attribute.Version); + } + + + protected void MvcSingletons(ContainerBuilder builder) { + builder.RegisterInstance(ControllerBuilder.Current); + builder.RegisterInstance(RouteTable.Routes); + builder.RegisterInstance(ModelBinders.Binders); + builder.RegisterInstance(ModelMetadataProviders.Current); + builder.RegisterInstance(ViewEngines.Engines); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj new file mode 100644 index 000000000..0f11fc754 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj @@ -0,0 +1,157 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {0DF8F426-9F30-4918-8F64-A5B40BA12D10} + {F85E285D-A4E0-4152-9332-AB1D724D3325};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.Azure.Web + Orchard.Azure.Web + v3.5 + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + False + ..\..\lib\autofac\Autofac.dll + + + False + ..\..\..\lib\autofac\Autofac.Configuration.dll + + + False + ..\..\lib\autofac\Autofac.Integration.Web.dll + + + False + ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll + + + False + ..\..\..\lib\Castle Windsor 2.0\bin\Castle.Core.dll + + + False + ..\..\..\lib\Castle Windsor 2.0\bin\Castle.DynamicProxy2.dll + + + + + + False + ..\..\lib\fluentnhibernate\NHibernate.ByteCode.Castle.dll + + + + + 3.5 + + + 3.5 + + + 3.5 + + + False + ..\..\lib\sqlite\x64\System.Data.SQLite.DLL + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 2\\Assemblies\System.Web.Mvc.dll + True + + + 3.5 + + + + + + + + + + + + + False + bin\Orchard.Framework.dll + + + False + bin\Orchard.Azure.dll + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 60453 + / + + + False + False + + + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs b/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c02dd9f71 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.Azure.Web")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Azure.Web")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("76077e05-d95f-49c7-b11a-2b0fc9176e7c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Web.config b/src/Orchard.Azure/Orchard.Azure.Web/Web.config new file mode 100644 index 000000000..b1de2f004 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Web.config @@ -0,0 +1,158 @@ + + + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs b/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs new file mode 100644 index 000000000..e5b12b130 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Microsoft.WindowsAzure; +using Microsoft.WindowsAzure.Diagnostics; +using Microsoft.WindowsAzure.ServiceRuntime; + +namespace Orchard.Azure.Web { + public class WebRole : RoleEntryPoint { + public override bool OnStart() { + DiagnosticMonitor.Start("DiagnosticsConnectionString"); + + #region Setup CloudStorageAccount Configuration Setting Publisher + + // This code sets up a handler to update CloudStorageAccount instances when their corresponding + // configuration settings change in the service configuration file. + CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { + // Provide the configSetter with the initial value + configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)); + + RoleEnvironment.Changed += (sender, arg) => { + if ( arg.Changes.OfType() + .Any(change => ( change.ConfigurationSettingName == configName )) ) { + // The corresponding configuration setting has changed, propagate the value + if ( !configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)) ) { + // In this case, the change to the storage account credentials in the + // service configuration is significant enough that the role needs to be + // recycled in order to use the latest settings. (for example, the + // endpoint has changed) + RoleEnvironment.RequestRecycle(); + } + } + }; + }); + #endregion + + + // For information on handling configuration changes + // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. + RoleEnvironment.Changing += (sender, e) => { + // If a configuration setting is changing + if ( + e.Changes.Any( + change => change is RoleEnvironmentConfigurationSettingChange) ) { + // Set e.Cancel to true to restart this role instance + e.Cancel = true; + } + }; + + return base.OnStart(); + } + + } +} diff --git a/src/Orchard.Azure/Orchard.Azure.csproj b/src/Orchard.Azure/Orchard.Azure.csproj index f23dc2e67..21becee28 100644 --- a/src/Orchard.Azure/Orchard.Azure.csproj +++ b/src/Orchard.Azure/Orchard.Azure.csproj @@ -51,9 +51,11 @@ + + diff --git a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs index 2181919c8..938a16602 100644 --- a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs +++ b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs @@ -1,250 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.WindowsAzure; -using Microsoft.WindowsAzure.StorageClient; -using System.IO; +using Microsoft.WindowsAzure; using Orchard.Storage; namespace Orchard.Azure.Storage { - public interface IDependency { } - public class AzureBlobStorageProvider : IStorageProvider { - public const string ContainerName = "media"; // container names must be lower cased - - private readonly CloudStorageAccount _storageAccount; - private string _shellName; - public CloudBlobClient BlobClient { get; private set; } - public CloudBlobContainer Container { get; private set; } + public class AzureBlobStorageProvider : AzureFileSystem, IStorageProvider { public AzureBlobStorageProvider(string shellName) : this(shellName, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) { } - public AzureBlobStorageProvider(string shellName, CloudStorageAccount storageAccount) { - // Setup the connection to custom storage accountm, e.g. Development Storage - _storageAccount = storageAccount; - - _shellName = shellName; - - BlobClient = _storageAccount.CreateCloudBlobClient(); - // Get and create the container if it does not exist - // The container is named with DNS naming restrictions (i.e. all lower case) - Container = BlobClient.GetContainerReference(ContainerName); - Container.CreateIfNotExist(); - - Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); - } - - private static void EnsurePathIsRelative(string path) { - if ( path.StartsWith("/") || path.StartsWith("http://")) - throw new ArgumentException("Path must be relative"); - } - - public IStorageFile GetFile(string path) { - EnsurePathIsRelative(path); - Container.EnsureBlobExists(path); - return new AzureBlobFileStorage(Container.GetBlockBlobReference(path)); - } - - public IEnumerable ListFiles(string path) { - EnsurePathIsRelative(path); - - string prefix = String.Concat(Container.Name, "/", _shellName, "/", path); - if ( !prefix.EndsWith("/") ) - prefix += "/"; - - foreach ( var blobItem in BlobClient.ListBlobsWithPrefix(prefix).OfType() ) { - yield return new AzureBlobFileStorage(blobItem); - } - } - - public IEnumerable ListFolders(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - if ( !Container.DirectoryExists(path) ) { - try { - CreateFolder(path); - } - catch ( Exception ex ) { - throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); - } - } - - return Container.GetDirectoryReference(path) - .ListBlobs() - .OfType() - .Select(d => new AzureBlobFolderStorage(d)) - .ToList(); - } - - public void CreateFolder(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureDirectoryDoesNotExist(path); - Container.GetDirectoryReference(path); - } - - public void DeleteFolder(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureDirectoryExists(path); - foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) { - if ( blob is CloudBlob ) - ( (CloudBlob)blob ).Delete(); - - if ( blob is CloudBlobDirectory ) - DeleteFolder(blob.Uri.ToString().Substring(Container.Uri.ToString().Length + 2 + _shellName.Length)); - } - } - - public void RenameFolder(string path, string newPath) { - EnsurePathIsRelative(path); - - EnsurePathIsRelative(newPath); - - if ( !path.EndsWith("/") ) - path += "/"; - - if ( !newPath.EndsWith("/") ) - newPath += "/"; - - foreach ( var blob in Container.GetDirectoryReference(_shellName + "/" + path).ListBlobs() ) { - if ( blob is CloudBlob ) { - string filename = Path.GetFileName(blob.Uri.ToString()); - string source = String.Concat(path, filename); - string destination = String.Concat(newPath, filename); - RenameFile(source, destination); - } - - if ( blob is CloudBlobDirectory ) { - string foldername = blob.Uri.Segments.Last(); - string source = String.Concat(path, foldername); - string destination = String.Concat(newPath, foldername); - RenameFolder(source, destination); - } - } - - } - - public void DeleteFile(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureBlobExists(path); - var blob = Container.GetBlockBlobReference(path); - blob.Delete(); - } - - public void RenameFile(string path, string newPath) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - EnsurePathIsRelative(newPath); - newPath = String.Concat(_shellName, "/", newPath); - - Container.EnsureBlobExists(path); - Container.EnsureBlobDoesNotExist(newPath); - - var blob = Container.GetBlockBlobReference(path); - var newBlob = Container.GetBlockBlobReference(newPath); - newBlob.CopyFromBlob(blob); - blob.Delete(); - } - - public IStorageFile CreateFile(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - if ( Container.BlobExists(path) ) { - throw new ArgumentException("File " + path + " already exists"); - } - - var blob = Container.GetBlockBlobReference(path); - blob.OpenWrite().Dispose(); // force file creation - return new AzureBlobFileStorage(blob); - } - - private class AzureBlobFileStorage : IStorageFile { - private readonly CloudBlockBlob _blob; - - public AzureBlobFileStorage(CloudBlockBlob blob) { - _blob = blob; - } - - public string GetPath() { - return _blob.Uri.ToString(); - } - - public string GetName() { - return Path.GetFileName(GetPath()); - } - - public long GetSize() { - return _blob.Properties.Length; - } - - public DateTime GetLastUpdated() { - return _blob.Properties.LastModifiedUtc; - } - - public string GetFileType() { - return Path.GetExtension(GetPath()); - } - - public Stream OpenRead() { - return _blob.OpenRead(); - } - - public Stream OpenWrite() { - return _blob.OpenWrite(); - } - - } - - private class AzureBlobFolderStorage : IStorageFolder { - private readonly CloudBlobDirectory _blob; - - public AzureBlobFolderStorage(CloudBlobDirectory blob) { - _blob = blob; - } - - public string GetName() { - return _blob.Uri.ToString(); - } - - public long GetSize() { - return GetDirectorySize(_blob); - } - - public DateTime GetLastUpdated() { - return DateTime.MinValue; - } - - public IStorageFolder GetParent() { - if ( _blob.Parent != null ) { - return new AzureBlobFolderStorage(_blob.Parent); - } - throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory"); - } - - private static long GetDirectorySize(CloudBlobDirectory directoryBlob) { - long size = 0; - - foreach ( var blobItem in directoryBlob.ListBlobs() ) { - if ( blobItem is CloudBlob ) - size += ( (CloudBlob)blobItem ).Properties.Length; - - if ( blobItem is CloudBlobDirectory ) - size += GetDirectorySize((CloudBlobDirectory)blobItem); - } - - return size; - } - } - + public AzureBlobStorageProvider(string shellName, CloudStorageAccount storageAccount) : base("media", shellName, false, storageAccount) { } } } + + diff --git a/src/Orchard.Web/Core/Common/Module.txt b/src/Orchard.Web/Core/Common/Module.txt index a4acb8fdf..88c8138b1 100644 --- a/src/Orchard.Web/Core/Common/Module.txt +++ b/src/Orchard.Web/Core/Common/Module.txt @@ -1,2 +1,10 @@ Name: Common -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Common: + Description: Core content parts. + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Dashboard/Module.txt b/src/Orchard.Web/Core/Dashboard/Module.txt index a07508ed4..e646ae5c2 100644 --- a/src/Orchard.Web/Core/Dashboard/Module.txt +++ b/src/Orchard.Web/Core/Dashboard/Module.txt @@ -1,2 +1,11 @@ name: Dashboard -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Dashboard: + Description: Standard admin dashboard. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Feeds/Module.txt b/src/Orchard.Web/Core/Feeds/Module.txt index 9ea4cb63b..1b2ba39c1 100644 --- a/src/Orchard.Web/Core/Feeds/Module.txt +++ b/src/Orchard.Web/Core/Feeds/Module.txt @@ -1,2 +1,11 @@ name: Feeds -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Feeds: + Description: RSS feeds for content items. + Dependencies: Common + Category: Syndication \ No newline at end of file diff --git a/src/Orchard.Web/Core/HomePage/Module.txt b/src/Orchard.Web/Core/HomePage/Module.txt index 2e1fcd1c1..b4a95af7e 100644 --- a/src/Orchard.Web/Core/HomePage/Module.txt +++ b/src/Orchard.Web/Core/HomePage/Module.txt @@ -1,2 +1,11 @@ name: HomePage -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + HomePage: + Description: Standard site home page that allows a specified content type or container to *be* the home page. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Navigation/Module.txt b/src/Orchard.Web/Core/Navigation/Module.txt index 21745169e..adbfd4438 100644 --- a/src/Orchard.Web/Core/Navigation/Module.txt +++ b/src/Orchard.Web/Core/Navigation/Module.txt @@ -1,2 +1,11 @@ name: Navigation -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Navigation: + Description: Menu management. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Scheduling/Module.txt b/src/Orchard.Web/Core/Scheduling/Module.txt index 28fac8bd4..848b44124 100644 --- a/src/Orchard.Web/Core/Scheduling/Module.txt +++ b/src/Orchard.Web/Core/Scheduling/Module.txt @@ -1,2 +1,11 @@ name: Scheduling -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Scheduling: + Description: Scheduled background tasks. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Module.txt b/src/Orchard.Web/Core/Settings/Module.txt index feb256afe..b5cd0527d 100644 --- a/src/Orchard.Web/Core/Settings/Module.txt +++ b/src/Orchard.Web/Core/Settings/Module.txt @@ -1,2 +1,11 @@ name: Settings antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Settings: + Description: Site settings. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/XmlRpc/Module.txt b/src/Orchard.Web/Core/XmlRpc/Module.txt index b94280d62..5db3d574c 100644 --- a/src/Orchard.Web/Core/XmlRpc/Module.txt +++ b/src/Orchard.Web/Core/XmlRpc/Module.txt @@ -1,2 +1,11 @@ Name: XmlRpc -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + XmlRpc: + Description: XML-RPC opt-in implementation. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Futures.Widgets/Module.txt b/src/Orchard.Web/Modules/Futures.Widgets/Module.txt index 721cd8cd0..ccc415c58 100644 --- a/src/Orchard.Web/Modules/Futures.Widgets/Module.txt +++ b/src/Orchard.Web/Modules/Futures.Widgets/Module.txt @@ -1,2 +1,11 @@ name: Widgets -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Futures.Widgets: + Description: Widgets container with simple inline content editing widget. + Dependencies: Common + Category: Widget \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt index 67d16c464..52f596e33 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Module.txt @@ -1,7 +1,11 @@ name: Blogs antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 features: Orchard.Blogs: - Description: A simple web log + Description: A simple web log. Dependencies: Common, XmlRpc - Category: Blog \ No newline at end of file + Category: Content \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Module.txt b/src/Orchard.Web/Modules/Orchard.Comments/Module.txt index d189b515f..7936c3af0 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Comments/Module.txt @@ -1,2 +1,11 @@ name: Comments -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Comments: + Description: Standard content item comments. + Dependencies: Common + Category: User Interaction \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt b/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt index 022c35fc0..0d8dbccc9 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt @@ -1 +1,11 @@ name: DevTools +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.DevTools: + Description: An assortment of debuging tools. + Dependencies: Common + Category: Developer \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Media/Module.txt b/src/Orchard.Web/Modules/Orchard.Media/Module.txt index 39454c683..4f96e9b36 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Media/Module.txt @@ -1,2 +1,11 @@ name: Media -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Media: + Description: File system based media upload, storage and management. + Dependencies: Common + Category: Media \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Module.txt b/src/Orchard.Web/Modules/Orchard.Modules/Module.txt index 8cdff6082..8414688c6 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Modules/Module.txt @@ -1,2 +1,11 @@ name: Modules antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Modules: + Description: Standard module and feature management. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx index dd762aeed..73ceb2487 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.ascx @@ -18,11 +18,11 @@ using (Html.BeginFormAntiForgeryPost()) { %>
    <% - foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Name).GroupBy(f => f.Descriptor.Category)) { %> + foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Category).GroupBy(f => f.Descriptor.Category)) { %> > -

    <%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("General")) %>

    +

    <%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("Uncategorized")) %>

      <% - foreach (var feature in featureGroup) {%> + foreach (var feature in featureGroup.OrderBy(f => f.Descriptor.Name)) {%> id="<%=Html.Encode(feature.Descriptor.Name) %>">
      @@ -39,7 +39,7 @@ using (Html.BeginFormAntiForgeryPost()) { %> } %> <% //dependencies - if (feature.Descriptor.Dependencies.Count() > 0) { %> + if (feature.Descriptor.Dependencies != null && feature.Descriptor.Dependencies.Count() > 0) { %>
    •  | <%=T("Depends on: {0}", string.Join(", ", feature.Descriptor.Dependencies.Select(s => Html.Link(Html.Encode(s), string.Format("{0}#{1}", Url.Action("features", new { area = "Orchard.Modules" }), Html.Encode(s)))).OrderBy(s => s).ToArray())) %>
    • <% } %>
    diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Module.txt b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Module.txt index d63affb83..53c124af0 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Module.txt @@ -1,2 +1,11 @@ name: MultiTenancy -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.MultiTenancy: + Description: Configure multiple site tenants. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Pages/Module.txt b/src/Orchard.Web/Modules/Orchard.Pages/Module.txt index ab71cbf1e..1ade81120 100644 --- a/src/Orchard.Web/Modules/Orchard.Pages/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Pages/Module.txt @@ -1,2 +1,11 @@ name: Pages -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Pages: + Description: Simple pages. + Dependencies: Common + Category: Content \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Roles/Module.txt b/src/Orchard.Web/Modules/Orchard.Roles/Module.txt index 274a90088..32fd2408d 100644 --- a/src/Orchard.Web/Modules/Orchard.Roles/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Roles/Module.txt @@ -1,2 +1,11 @@ name: Roles -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Roles: + Description: Standard user roles. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Sandbox/Module.txt b/src/Orchard.Web/Modules/Orchard.Sandbox/Module.txt index ca967af74..488adb7de 100644 --- a/src/Orchard.Web/Modules/Orchard.Sandbox/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Sandbox/Module.txt @@ -1 +1,11 @@ name: Sandbox +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Sandbox: + Description: A module to mess around with. Currently wiki-like. + Dependencies: Common + Category: Developer \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj b/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj index ccde29bd6..54b613b8f 100644 --- a/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj +++ b/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj @@ -79,7 +79,7 @@ - + diff --git a/src/Orchard.Web/Modules/Orchard.Sandbox/_Module.txt b/src/Orchard.Web/Modules/Orchard.Sandbox/_Module.txt deleted file mode 100644 index ca967af74..000000000 --- a/src/Orchard.Web/Modules/Orchard.Sandbox/_Module.txt +++ /dev/null @@ -1 +0,0 @@ -name: Sandbox diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Module.txt b/src/Orchard.Web/Modules/Orchard.Setup/Module.txt index df08e832d..1d87f1edd 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Setup/Module.txt @@ -1,2 +1,11 @@ name: Setup antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Setup: + Description: Standard site setup. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Tags/Module.txt b/src/Orchard.Web/Modules/Orchard.Tags/Module.txt index 593c5da5b..2cc734ef8 100644 --- a/src/Orchard.Web/Modules/Orchard.Tags/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Tags/Module.txt @@ -1,2 +1,11 @@ name: Tags -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Tags: + Description: Tag a content item. + Dependencies: Common + Category: Taxonomy \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Module.txt b/src/Orchard.Web/Modules/Orchard.Themes/Module.txt index 4b65cc663..f2c729ec6 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Themes/Module.txt @@ -1,2 +1,11 @@ name: Themes antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Themes: + Description: Basic theming capability. + Dependencies: Common + Category: Display \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Users/Module.txt b/src/Orchard.Web/Modules/Orchard.Users/Module.txt index 3d70e2e47..7cf61082f 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Users/Module.txt @@ -1,2 +1,11 @@ name: Users -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + Orchard.Users: + Description: Standard users. + Dependencies: Common + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Modules/TinyMce/Module.txt b/src/Orchard.Web/Modules/TinyMce/Module.txt index 1e0ca4ce7..9e79c1dfc 100644 --- a/src/Orchard.Web/Modules/TinyMce/Module.txt +++ b/src/Orchard.Web/Modules/TinyMce/Module.txt @@ -1,2 +1,10 @@ name: TinyMce -antiforgery: enabled \ No newline at end of file +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +features: + TinyMce: + Description: TinyMCE HTML WYSIWYG editor. + Category: Input Editor \ No newline at end of file