From 7248fb6fd73ed8c6036686ca0286463a433713ed Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 7 May 2010 12:41:37 -0700 Subject: [PATCH] Azure packaging Executing ClickToBuild.cmd generates the package for Azure including all modules available in Orchard.Web automatically AzureSDK in requiered for this tasks. If not present, the package won't succeed, but Orchard.Web will still be built The Orchard.Azure solution contains the required environment for implementing Azure specific providers, and unit testing The Orchard.Azure.CloudService solution contains the CloudService enrironment to simulate Azure platform locally --HG-- branch : dev --- .hgignore | 1 + AzurePackage.proj | 137 +++++++++ build.cmd | 3 +- .../AzureShellSettingsManagerTests.cs | 6 + .../Storage/AzureBlobStorageProviderTests.cs | 4 - src/Orchard.Azure/AzureFileSystem.cs | 271 ++++++++++++++++++ .../Configuration/AzureAppDataFolder.cs | 65 +++++ .../Orchard.Azure.CloudService.sln | 38 +++ .../Orchard.Azure.CloudService.ccproj | 51 ++++ .../ServiceConfiguration.cscfg | 18 ++ .../ServiceDefinition.csdef | 12 + .../Config/Diagnostics.config | 27 ++ .../Orchard.Azure.Web/Config/Host.config | 37 +++ .../Orchard.Azure.Web/Global.asax | 1 + .../Orchard.Azure.Web/Global.asax.cs | 104 +++++++ .../Orchard.Azure.Web.csproj | 157 ++++++++++ .../Properties/AssemblyInfo.cs | 35 +++ .../Orchard.Azure.Web/Web.config | 158 ++++++++++ .../Orchard.Azure.Web/WebRole.cs | 52 ++++ src/Orchard.Azure/Orchard.Azure.csproj | 2 + .../Storage/AzureBlobStorageProvider.cs | 244 +--------------- 21 files changed, 1179 insertions(+), 244 deletions(-) create mode 100644 AzurePackage.proj create mode 100644 src/Orchard.Azure/AzureFileSystem.cs create mode 100644 src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs create mode 100644 src/Orchard.Azure/Orchard.Azure.CloudService.sln create mode 100644 src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj create mode 100644 src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg create mode 100644 src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Global.asax create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/Web.config create mode 100644 src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs diff --git a/.hgignore b/.hgignore index 349eab9bb..9f8e4dd39 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) { } } } + +