From 152d8dea15fb5942c7613e7e6fb9975a45e43921 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Mon, 26 Apr 2010 11:36:55 -0700 Subject: [PATCH 1/3] Updating tracing and adding specflow support for command handlers --HG-- branch : dev --- src/Orchard.Specs/Bindings/CommandLine.cs | 34 +++++++++++++++++++ .../Bindings/OrchardSiteFactory.cs | 25 +++++++++++++- src/Orchard.Specs/Bindings/WebAppHosting.cs | 29 +++++++++------- .../Orchard.Web/Config/Diagnostics.config | 3 -- .../{Sites.Default.config => Sites.config} | 0 .../TraceEnabledSessionFactoryBuilder.cs | 3 +- src/Orchard.Specs/Hosting/WebHost.cs | 1 + src/Orchard.Specs/MultiTenancy.feature | 8 +++++ src/Orchard.Specs/MultiTenancy.feature.cs | 23 +++++++++++++ src/Orchard.Specs/Orchard.Specs.csproj | 7 +++- src/Orchard/Environment/IOrchardHost.cs | 1 + .../ShellBuilders/ShellContainerFactory.cs | 10 ++++-- 12 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 src/Orchard.Specs/Bindings/CommandLine.cs rename src/Orchard.Specs/Hosting/Orchard.Web/Config/{Sites.Default.config => Sites.config} (100%) diff --git a/src/Orchard.Specs/Bindings/CommandLine.cs b/src/Orchard.Specs/Bindings/CommandLine.cs new file mode 100644 index 000000000..50d0e361e --- /dev/null +++ b/src/Orchard.Specs/Bindings/CommandLine.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using System.Linq; +using Orchard.Commands; +using Orchard.Parameters; +using Orchard.Specs.Hosting; +using TechTalk.SpecFlow; + +namespace Orchard.Specs.Bindings { + [Binding] + public class CommandLine : BindingBase { + [When(@"I execute >(.*)")] + public void WhenIExecute(string commandLine) { + var details = new RequestDetails(); + Binding().Host.Execute(() => { + var args = commandLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var parameters = new CommandParametersParser().Parse(args); + var agent = new CommandHostAgent(); + var input = new StringReader(""); + var output = new StringWriter(); + details.StatusCode = agent.RunSingleCommand( + input, + output, + "Default", + parameters.Arguments.ToArray(), + parameters.Switches.ToDictionary(kv => kv.Key, kv => kv.Value)); + details.StatusDescription = details.StatusCode.ToString(); + details.ResponseText = output.ToString(); + }); + + Binding().Details = details; + } + } +} diff --git a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs index 423c8980a..605692f79 100644 --- a/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs +++ b/src/Orchard.Specs/Bindings/OrchardSiteFactory.cs @@ -47,7 +47,7 @@ namespace Orchard.Specs.Bindings { descriptor.EnabledFeatures.Concat(new[] { new ShellFeature { Name = name } }), descriptor.Parameters); } - + Trace.WriteLine("This call to Host.Reinitialize should not be needed, eventually"); MvcApplication.Host.Reinitialize_Obsolete(); }); @@ -62,7 +62,30 @@ namespace Orchard.Specs.Bindings { Trace.WriteLine("This call to Host.Reinitialize should not be needed, eventually"); MvcApplication.Host.Reinitialize_Obsolete(); }); + } + [Given(@"I have tenant ""(.*)\"" on ""(.*)\"" as ""(.*)\""")] + public void GivenIHaveTenantOnSiteAsName(string shellName, string hostName, string siteName) { + var webApp = Binding(); + webApp.Host.Execute(() => { + var shellSettings = new ShellSettings { + Name = shellName, + RequestUrlHost = hostName, + State = new TenantState("Uninitialized"), + }; + using (var environment = MvcApplication.CreateStandaloneEnvironment("Default")) { + environment.Resolve().SaveSettings(shellSettings); + } + MvcApplication.Host.Reinitialize_Obsolete(); + }); + + webApp.WhenIGoToPathOnHost("Setup", hostName); + + webApp.WhenIFillIn(TableData( + new { name = "SiteName", value = siteName }, + new { name = "AdminPassword", value = "6655321" })); + + webApp.WhenIHit("Finish Setup"); } } } diff --git a/src/Orchard.Specs/Bindings/WebAppHosting.cs b/src/Orchard.Specs/Bindings/WebAppHosting.cs index 6b6fa82a5..99e439472 100644 --- a/src/Orchard.Specs/Bindings/WebAppHosting.cs +++ b/src/Orchard.Specs/Bindings/WebAppHosting.cs @@ -30,6 +30,11 @@ namespace Orchard.Specs.Bindings { get { return _webHost; } } + public RequestDetails Details { + get { return _details; } + set { _details = value; } + } + [Given(@"I have a clean site")] public void GivenIHaveACleanSite() { GivenIHaveACleanSiteBasedOn("Orchard.Web"); @@ -122,16 +127,16 @@ namespace Orchard.Specs.Bindings { [When(@"I go to ""(.*)"" on host (.*)")] public void WhenIGoToPathOnHost(string urlPath, string host) { Host.HostName = host; - _details = Host.SendRequest(urlPath); + Details = Host.SendRequest(urlPath); _doc = new HtmlDocument(); - _doc.Load(new StringReader(_details.ResponseText)); + _doc.Load(new StringReader(Details.ResponseText)); } [When(@"I go to ""(.*)""")] public void WhenIGoTo(string urlPath) { - _details = Host.SendRequest(urlPath); + Details = Host.SendRequest(urlPath); _doc = new HtmlDocument(); - _doc.Load(new StringReader(_details.ResponseText)); + _doc.Load(new StringReader(Details.ResponseText)); } [When(@"I follow ""(.*)""")] @@ -153,7 +158,7 @@ namespace Orchard.Specs.Bindings { foreach (var row in table.Rows) { var r = row; var input = inputs.SingleOrDefault(x => x.GetAttributeValue("name", x.GetAttributeValue("id", "")) == r["name"]); - Assert.That(input, Is.Not.Null, "Unable to locate name {0} in page html:\r\n\r\n{1}", r["name"], _details.ResponseText); + Assert.That(input, Is.Not.Null, "Unable to locate name {0} in page html:\r\n\r\n{1}", r["name"], Details.ResponseText); input.Attributes.Add("value", row["value"]); } } @@ -165,21 +170,21 @@ namespace Orchard.Specs.Bindings { .Single(elt => elt.GetAttributeValue("value", null) == submitText); var form = Form.LocateAround(submit); - var urlPath = form.Start.GetAttributeValue("action", _details.UrlPath); + var urlPath = form.Start.GetAttributeValue("action", Details.UrlPath); var inputs = form.Children .SelectMany(elt => elt.DescendantsAndSelf("input")) .GroupBy(elt => elt.GetAttributeValue("name", elt.GetAttributeValue("id", "")), elt => elt.GetAttributeValue("value", "")) .ToDictionary(elt => elt.Key, elt => (IEnumerable)elt); - _details = Host.SendRequest(urlPath, inputs); + Details = Host.SendRequest(urlPath, inputs); _doc = new HtmlDocument(); - _doc.Load(new StringReader(_details.ResponseText)); + _doc.Load(new StringReader(Details.ResponseText)); } [When(@"I am redirected")] public void WhenIAmRedirected() { var urlPath = ""; - if (_details.ResponseHeaders.TryGetValue("Location", out urlPath)) { + if (Details.ResponseHeaders.TryGetValue("Location", out urlPath)) { WhenIGoTo(urlPath); } else { @@ -189,13 +194,13 @@ namespace Orchard.Specs.Bindings { [Then(@"the status should be (.*) (.*)")] public void ThenTheStatusShouldBe(int statusCode, string statusDescription) { - Assert.That(_details.StatusCode, Is.EqualTo(statusCode)); - Assert.That(_details.StatusDescription, Is.EqualTo(statusDescription)); + Assert.That(Details.StatusCode, Is.EqualTo(statusCode)); + Assert.That(Details.StatusDescription, Is.EqualTo(statusDescription)); } [Then(@"I should see ""(.*)""")] public void ThenIShouldSee(string text) { - Assert.That(_details.ResponseText, Is.StringContaining(text)); + Assert.That(Details.ResponseText, Is.StringContaining(text)); } [Then(@"the title contains ""(.*)""")] diff --git a/src/Orchard.Specs/Hosting/Orchard.Web/Config/Diagnostics.config b/src/Orchard.Specs/Hosting/Orchard.Web/Config/Diagnostics.config index f65fef8c7..a44c108d4 100644 --- a/src/Orchard.Specs/Hosting/Orchard.Web/Config/Diagnostics.config +++ b/src/Orchard.Specs/Hosting/Orchard.Web/Config/Diagnostics.config @@ -26,9 +26,6 @@ - - diff --git a/src/Orchard.Specs/Hosting/Orchard.Web/Config/Sites.Default.config b/src/Orchard.Specs/Hosting/Orchard.Web/Config/Sites.config similarity index 100% rename from src/Orchard.Specs/Hosting/Orchard.Web/Config/Sites.Default.config rename to src/Orchard.Specs/Hosting/Orchard.Web/Config/Sites.config diff --git a/src/Orchard.Specs/Hosting/TraceEnabledSessionFactoryBuilder.cs b/src/Orchard.Specs/Hosting/TraceEnabledSessionFactoryBuilder.cs index f05e8a392..cb82f295a 100644 --- a/src/Orchard.Specs/Hosting/TraceEnabledSessionFactoryBuilder.cs +++ b/src/Orchard.Specs/Hosting/TraceEnabledSessionFactoryBuilder.cs @@ -17,7 +17,8 @@ namespace Orchard.Specs.Hosting { } protected override IPersistenceConfigurer GetPersistenceConfigurer(bool createDatabase) { var config = (SQLiteConfiguration)base.GetPersistenceConfigurer(createDatabase); - return config.ShowSql(); + //config.ShowSql(); + return config; } } } diff --git a/src/Orchard.Specs/Hosting/WebHost.cs b/src/Orchard.Specs/Hosting/WebHost.cs index c188f76e7..d834cbe01 100644 --- a/src/Orchard.Specs/Hosting/WebHost.cs +++ b/src/Orchard.Specs/Hosting/WebHost.cs @@ -28,6 +28,7 @@ namespace Orchard.Specs.Hosting { baseDir .ShallowCopy("*.dll", _tempSite.Combine("bin")) + .ShallowCopy("*.exe", _tempSite.Combine("bin")) .ShallowCopy("*.pdb", _tempSite.Combine("bin")); HostName = "localhost"; diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature index a32b0ab39..ed5bbed0b 100644 --- a/src/Orchard.Specs/MultiTenancy.feature +++ b/src/Orchard.Specs/MultiTenancy.feature @@ -74,3 +74,11 @@ Scenario: A new tenant runs the setup And I go to "/Default.aspx" Then I should see "

Scott Site

" And I should see "Welcome, admin!" + +Scenario: Listing tenants from command line + Given I have installed Orchard + And I have installed "Orchard.MultiTenancy" + And I have tenant "Alpha" on "example.org" as "New-site-name" + When I execute >orchard tenant list + Then I should see "Name: Alpha" + And I should see "Request Url Host: example.org" diff --git a/src/Orchard.Specs/MultiTenancy.feature.cs b/src/Orchard.Specs/MultiTenancy.feature.cs index 4dd7fc195..701a66ee0 100644 --- a/src/Orchard.Specs/MultiTenancy.feature.cs +++ b/src/Orchard.Specs/MultiTenancy.feature.cs @@ -250,6 +250,29 @@ this.ScenarioSetup(scenarioInfo); testRunner.Then("I should see \"

Scott Site

\""); #line 76 testRunner.And("I should see \"Welcome, admin!\""); +#line hidden + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Listing tenants from command line")] + public virtual void ListingTenantsFromCommandLine() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Listing tenants from command line", ((string[])(null))); +#line 78 +this.ScenarioSetup(scenarioInfo); +#line 79 + testRunner.Given("I have installed Orchard"); +#line 80 + testRunner.And("I have installed \"Orchard.MultiTenancy\""); +#line 81 + testRunner.And("I have tenant \"Alpha\" on \"example.org\" as \"New-site-name\""); +#line 82 + testRunner.When("I execute >tenant list"); +#line 83 + testRunner.Then("I should see \"Name: Alpha\""); +#line 84 + testRunner.And("I should see \"Request Url Host: example.org\""); #line hidden testRunner.CollectScenarioErrors(); } diff --git a/src/Orchard.Specs/Orchard.Specs.csproj b/src/Orchard.Specs/Orchard.Specs.csproj index 96cf0b66b..f6bfdc2d0 100644 --- a/src/Orchard.Specs/Orchard.Specs.csproj +++ b/src/Orchard.Specs/Orchard.Specs.csproj @@ -103,6 +103,7 @@ + @@ -158,7 +159,7 @@ Always - + Always @@ -248,6 +249,10 @@ {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} Orchard.Framework + + {33B1BC8D-E292-4972-A363-22056B207156} + Orchard + diff --git a/src/Orchard/Environment/IOrchardHost.cs b/src/Orchard/Environment/IOrchardHost.cs index be434f7a7..314560677 100644 --- a/src/Orchard/Environment/IOrchardHost.cs +++ b/src/Orchard/Environment/IOrchardHost.cs @@ -28,4 +28,5 @@ namespace Orchard.Environment { /// IStandaloneEnvironment CreateStandaloneEnvironment(ShellSettings shellSettings); } + } diff --git a/src/Orchard/Environment/ShellBuilders/ShellContainerFactory.cs b/src/Orchard/Environment/ShellBuilders/ShellContainerFactory.cs index 0cf188489..2db70da88 100644 --- a/src/Orchard/Environment/ShellBuilders/ShellContainerFactory.cs +++ b/src/Orchard/Environment/ShellBuilders/ShellContainerFactory.cs @@ -87,9 +87,13 @@ namespace Orchard.Environment.ShellBuilders { .InjectActionInvoker(); } - var optionalHostConfig = HostingEnvironment.MapPath("~/Config/Sites." + settings.Name + ".config"); - if (File.Exists(optionalHostConfig)) - builder.RegisterModule(new ConfigurationSettingsReader(ConfigurationSettingsReader.DefaultSectionName, optionalHostConfig)); + var optionalShellConfig = HostingEnvironment.MapPath("~/Config/Sites.config"); + if (File.Exists(optionalShellConfig)) + builder.RegisterModule(new ConfigurationSettingsReader(ConfigurationSettingsReader.DefaultSectionName, optionalShellConfig)); + + var optionalShellByNameConfig = HostingEnvironment.MapPath("~/Config/Sites." + settings.Name + ".config"); + if (File.Exists(optionalShellByNameConfig)) + builder.RegisterModule(new ConfigurationSettingsReader(ConfigurationSettingsReader.DefaultSectionName, optionalShellByNameConfig)); }); } From 9efcf65058f858d3d6d9ca9af388660e34293f91 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 23 Apr 2010 17:39:23 -0700 Subject: [PATCH 2/3] Implemented AzureBlobStorageProvider Added unit tests Created a specific solution with Azure dependencies --HG-- branch : dev --- src/Orchard.Azure.Tests/App.config | 6 + .../Orchard.Azure.Tests.csproj | 80 +++++ .../Properties/AssemblyInfo.cs | 36 ++ .../Storage/AzureBlobStorageProviderTests.cs | 190 ++++++++++ src/Orchard.Azure.sln | 32 ++ src/Orchard.Azure/Orchard.Azure.csproj | 66 ++++ src/Orchard.Azure/Properties/AssemblyInfo.cs | 36 ++ .../Storage/AzureBlobStorageProvider.cs | 327 ++++++++++++++++++ .../Storage/FileSystemStorageProvider.cs | 18 +- src/Orchard/Storage/IStorageFile.cs | 11 +- src/Orchard/Storage/IStorageProvider.cs | 9 + 11 files changed, 807 insertions(+), 4 deletions(-) create mode 100644 src/Orchard.Azure.Tests/App.config create mode 100644 src/Orchard.Azure.Tests/Orchard.Azure.Tests.csproj create mode 100644 src/Orchard.Azure.Tests/Properties/AssemblyInfo.cs create mode 100644 src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs create mode 100644 src/Orchard.Azure.sln create mode 100644 src/Orchard.Azure/Orchard.Azure.csproj create mode 100644 src/Orchard.Azure/Properties/AssemblyInfo.cs create mode 100644 src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs diff --git a/src/Orchard.Azure.Tests/App.config b/src/Orchard.Azure.Tests/App.config new file mode 100644 index 000000000..c244681d2 --- /dev/null +++ b/src/Orchard.Azure.Tests/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure.Tests/Orchard.Azure.Tests.csproj b/src/Orchard.Azure.Tests/Orchard.Azure.Tests.csproj new file mode 100644 index 000000000..231b57883 --- /dev/null +++ b/src/Orchard.Azure.Tests/Orchard.Azure.Tests.csproj @@ -0,0 +1,80 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {1CC62F45-E6FF-43D5-84BF-509A1085D994} + Library + Properties + Orchard.Azure.Tests + Orchard.Azure.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + False + ..\..\lib\nunit\nunit.framework.dll + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + {2505AA84-65A6-43D0-9C27-4F44FD576284} + Orchard.Azure + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure.Tests/Properties/AssemblyInfo.cs b/src/Orchard.Azure.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8c0fd184d --- /dev/null +++ b/src/Orchard.Azure.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Azure.Tests")] +[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("21e6aa35-d13d-495b-af35-bc066a1a8bf2")] + +// 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 Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs new file mode 100644 index 000000000..b2060dd5e --- /dev/null +++ b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs @@ -0,0 +1,190 @@ +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; + +namespace Orchard.Azure.Tests.Storage { + [TestFixture] + public class AzureBlobStorageProviderTests { + + #region Azure Environment initialization + + private Process _dsService; + + [TestFixtureSetUp] + public void FixtureSetup() { + var count = Process.GetProcessesByName("DSService").Length; + if (count == 0) + { + var start = new ProcessStartInfo + { + Arguments = "/devstore:start", + FileName = + Path.Combine(ConfigurationManager.AppSettings["AzureSDK"], @"bin\csrun.exe") + }; + + _dsService = new Process { StartInfo = start }; + _dsService.Start(); + _dsService.WaitForExit(); + } + + CloudStorageAccount devAccount; + CloudStorageAccount.TryParse("UseDevelopmentStorage=true", out devAccount); + + _azureBlobStorageProvider = new AzureBlobStorageProvider("default", false, devAccount); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + + if(_dsService != null) + _dsService.Close(); + } + + [SetUp] + public void Setup() { + // ensure default container is empty before running any test + DeleteAllBlobs(); + } + + #endregion + + private AzureBlobStorageProvider _azureBlobStorageProvider; + + private void DeleteAllBlobs() { + foreach(var blob in _azureBlobStorageProvider.Container.ListBlobs()) { + if(blob is CloudBlob) { + ((CloudBlob) blob).DeleteIfExists(); + } + + if (blob is CloudBlobDirectory) { + DeleteAllBlobs((CloudBlobDirectory)blob); + } + } + } + + private static void DeleteAllBlobs(CloudBlobDirectory cloudBlobDirectory) { + foreach (var blob in cloudBlobDirectory.ListBlobs()) { + if (blob is CloudBlob) { + ((CloudBlob)blob).DeleteIfExists(); + } + + if (blob is CloudBlobDirectory) { + DeleteAllBlobs((CloudBlobDirectory)blob); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetFileShouldOnlyAcceptRelativePath() { + _azureBlobStorageProvider.CreateFile("foo.txt"); + _azureBlobStorageProvider.GetFile("/foot.txt"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetFileThatDoesNotExistShouldThrow() { + _azureBlobStorageProvider.GetFile("notexisting"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void DeleteFileThatDoesNotExistShouldThrow() { + _azureBlobStorageProvider.DeleteFile("notexisting"); + } + + [Test] + public void CreateFileShouldReturnCorrectStorageFile() { + var storageFile = _azureBlobStorageProvider.CreateFile("foo.txt"); + + Assert.AreEqual(".txt", storageFile.GetFileType()); + Assert.AreEqual("foo.txt", storageFile.GetName()); + Assert.That(storageFile.GetPath().EndsWith("/default/foo.txt"), Is.True); + Assert.AreEqual(0, storageFile.GetSize()); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void CreateFileShouldThrowAnExceptionIfAlreadyExisting() { + var storageFile = _azureBlobStorageProvider.CreateFile("foo.txt"); + Assert.AreEqual(storageFile.GetSize(), 0); + + _azureBlobStorageProvider.CreateFile("foo.txt"); + } + + [Test] + public void RenameFileShouldCreateANewFileAndRemoveTheOld() { + _azureBlobStorageProvider.CreateFile("foo1.txt"); + _azureBlobStorageProvider.RenameFile("foo1.txt", "foo2.txt"); + + var files = _azureBlobStorageProvider.ListFiles(""); + + Assert.AreEqual(1, files.Count()); + Assert.That(files.First().GetPath().EndsWith("foo2.txt"), Is.True); + } + + [Test] + public void CreateFileShouldBeFolderAgnostic() + { + _azureBlobStorageProvider.CreateFile("foo.txt"); + _azureBlobStorageProvider.CreateFile("folder/foo.txt"); + _azureBlobStorageProvider.CreateFile("folder/folder/foo.txt"); + + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder/folder").Count()); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void CreateFolderThatExistsShouldThrow() { + // sebros: In Azure, the folder concept is just about checking files prefix. So until a file exists, a folder is nothing + _azureBlobStorageProvider.CreateFile("folder/foo.txt"); + _azureBlobStorageProvider.CreateFolder("folder"); + } + + [Test] + public void DeleteFolderShouldDeleteFilesAlso() { + _azureBlobStorageProvider.CreateFile("folder/foo1.txt"); + _azureBlobStorageProvider.CreateFile("folder/foo2.txt"); + _azureBlobStorageProvider.CreateFile("folder/folder/foo1.txt"); + _azureBlobStorageProvider.CreateFile("folder/folder/foo2.txt"); + + Assert.AreEqual(2, _azureBlobStorageProvider.ListFiles("folder").Count()); + Assert.AreEqual(2, _azureBlobStorageProvider.ListFiles("folder/folder").Count()); + + _azureBlobStorageProvider.DeleteFolder("folder"); + + Assert.AreEqual(0, _azureBlobStorageProvider.ListFiles("folder").Count()); + Assert.AreEqual(0, _azureBlobStorageProvider.ListFiles("folder/folder").Count()); + } + + [Test] + public void ShouldRenameFolders() { + _azureBlobStorageProvider.CreateFile("folder1/foo.txt"); + _azureBlobStorageProvider.CreateFile("folder2/foo.txt"); + _azureBlobStorageProvider.CreateFile("folder1/folder2/foo.txt"); + _azureBlobStorageProvider.CreateFile("folder1/folder2/folder3/foo.txt"); + + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder2").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1/folder2").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1/folder2/folder3").Count()); + + _azureBlobStorageProvider.RenameFolder("folder1/folder2", "folder1/folder4"); + + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder2").Count()); + Assert.AreEqual(0, _azureBlobStorageProvider.ListFiles("folder1/folder2").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1/folder4").Count()); + Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1/folder4/folder3").Count()); + } + + } +} diff --git a/src/Orchard.Azure.sln b/src/Orchard.Azure.sln new file mode 100644 index 000000000..6e952e846 --- /dev/null +++ b/src/Orchard.Azure.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure", "Orchard.Azure\Orchard.Azure.csproj", "{2505AA84-65A6-43D0-9C27-4F44FD576284}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.Tests", "Orchard.Azure.Tests\Orchard.Azure.Tests.csproj", "{1CC62F45-E6FF-43D5-84BF-509A1085D994}" +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 + {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 + {1CC62F45-E6FF-43D5-84BF-509A1085D994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CC62F45-E6FF-43D5-84BF-509A1085D994}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CC62F45-E6FF-43D5-84BF-509A1085D994}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CC62F45-E6FF-43D5-84BF-509A1085D994}.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.csproj b/src/Orchard.Azure/Orchard.Azure.csproj new file mode 100644 index 000000000..69c7f0ec3 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.csproj @@ -0,0 +1,66 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {2505AA84-65A6-43D0-9C27-4F44FD576284} + Library + Properties + Orchard.Azure + Orchard.Azure + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Properties/AssemblyInfo.cs b/src/Orchard.Azure/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..17cd66b92 --- /dev/null +++ b/src/Orchard.Azure/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Azure")] +[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("02bb8033-01ce-4c2d-a3f7-2a03641f4640")] + +// 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 Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs new file mode 100644 index 000000000..98515b67d --- /dev/null +++ b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs @@ -0,0 +1,327 @@ +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.Storage +{ + public interface IDependency {} + + public class AzureBlobStorageProvider : IStorageProvider + { + private readonly CloudStorageAccount _storageAccount; + public CloudBlobClient BlobClient { get; private set; } + public CloudBlobContainer Container { get; private set; } + + public AzureBlobStorageProvider(string containerName, bool isPrivate) : this(containerName, isPrivate, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) + { + } + + public AzureBlobStorageProvider(string containerName, bool isPrivate, CloudStorageAccount storageAccount) + { + // Setup the connection to custom storage accountm, e.g. Development Storage + _storageAccount = storageAccount; + InitBlobClient(containerName, isPrivate); + } + + private void InitBlobClient(string containerName, bool isPrivate) + { + 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); + + // Setup the permissions if the container is new + if (Container.CreateIfNotExist()) { + if (isPrivate) + Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Off }); + else + Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); + } + } + + private void EnsurePathIsRelative(string path) { + if(path.StartsWith("/")) + throw new ArgumentException("Path must be relative"); + } + + private string GetPrefix(string path) { + var prefix = String.Concat(Container.Name, "/", path); + if (prefix.EndsWith("/")) + return prefix; + + return String.Concat(prefix, "/"); + } + + private bool BlobExists(string path) { + if(String.IsNullOrEmpty(path) || path.Trim() == String.Empty) + throw new ArgumentException("Path can't be empty"); + + try { + var blob = Container.GetBlockBlobReference(path); + blob.FetchAttributes(); + return true; + } + catch (StorageClientException e) { + if (e.ErrorCode == StorageErrorCode.ResourceNotFound) { + return false; + } + + throw; + } + } + + private void EnsureBlobExists(string path) + { + if (!BlobExists(path)) { + throw new ArgumentException("File " + path + " does not exist"); + } + } + + private void EnsureBlobDoesNotExist(string path) + { + if (BlobExists(path)) { + throw new ArgumentException("File " + path + " already exists"); + } + } + + private bool DirectoryExists(string path) + { + if (String.IsNullOrEmpty(path) || path.Trim() == String.Empty) + throw new ArgumentException("Path can't be empty"); + + return Container.GetDirectoryReference(path).ListBlobs().Count() > 0; + } + + private void EnsureDirectoryExists(string path) + { + if (!DirectoryExists(path)) { + throw new ArgumentException("Directory " + path + " does not exist"); + } + } + + private void EnsureDirectoryDoesNotExist(string path) + { + if (DirectoryExists(path)) { + throw new ArgumentException("Directory " + path + " already exists"); + } + } + + #region IStorageProvider Members + + public IStorageFile GetFile(string path) { + EnsurePathIsRelative(path); + EnsureBlobExists(path); + return new AzureBlobFileStorage(Container.GetBlockBlobReference(path)); + } + + public IEnumerable ListFiles(string path) + { + EnsurePathIsRelative(path); + foreach (var blobItem in BlobClient.ListBlobsWithPrefix(GetPrefix(path)).OfType()) { + yield return new AzureBlobFileStorage(blobItem); + } + } + + public IEnumerable ListFolders(string path) + { + EnsurePathIsRelative(path); + if (!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); + EnsureDirectoryDoesNotExist(path); + Container.GetDirectoryReference(path); + } + + public void DeleteFolder(string path) { + 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()); + } + } + + 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(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); + EnsureBlobExists(path); + var blob = Container.GetBlockBlobReference(path); + blob.Delete(); + } + + public void RenameFile(string path, string newPath) { + EnsurePathIsRelative(path); + EnsurePathIsRelative(newPath); + EnsureBlobExists(path); + EnsureBlobDoesNotExist(newPath); + + var blob = Container.GetBlockBlobReference(path); + var newBlob = Container.GetBlockBlobReference(newPath); + newBlob.CopyFromBlob(blob); + blob.Delete(); + } + + public IStorageFile CreateFile(string path) { + EnsurePathIsRelative(path); + if (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 Combine(string path1, string path2) { + EnsurePathIsRelative(path1); + EnsurePathIsRelative(path2); + + if (path1 == null || path2 == null) + throw new ArgumentException("One or more path is null"); + + if (path1.Trim() == String.Empty) + return path2; + + if (path2.Trim() == String.Empty) + return path1; + + var uri1 = new Uri(path1); + var uri2 = new Uri(path2); + + return uri2.IsAbsoluteUri ? uri2.ToString() : new Uri(uri1, uri2).ToString(); + } + + #endregion + + 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; + } + + #region IStorageFolder Members + + 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; + } + + #endregion + } + } +} diff --git a/src/Orchard/Storage/FileSystemStorageProvider.cs b/src/Orchard/Storage/FileSystemStorageProvider.cs index a2a8eeaed..f60f63031 100644 --- a/src/Orchard/Storage/FileSystemStorageProvider.cs +++ b/src/Orchard/Storage/FileSystemStorageProvider.cs @@ -95,7 +95,7 @@ namespace Orchard.Storage { public void RenameFile(string path, string newPath) { if (!File.Exists(path)) { - throw new ArgumentException("File " + path + "does not exist"); + throw new ArgumentException("File " + path + " does not exist"); } if (File.Exists(newPath)) { @@ -105,6 +105,11 @@ namespace Orchard.Storage { File.Move(path, newPath); } + public string Combine(string path1, string path2) + { + return Path.Combine(path1, path2); + } + #endregion private class FileSystemStorageFile : IStorageFile { @@ -136,8 +141,14 @@ namespace Orchard.Storage { return _fileInfo.Extension; } - public Stream OpenStream() { - return new FileStream(_fileInfo.FullName, FileMode.Open); + public Stream OpenRead() + { + return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Read); + } + + public Stream OpenWrite() + { + return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.ReadWrite); } #endregion @@ -192,5 +203,6 @@ namespace Orchard.Storage { return size; } } + } } diff --git a/src/Orchard/Storage/IStorageFile.cs b/src/Orchard/Storage/IStorageFile.cs index bb47cd53d..bd7177bd4 100644 --- a/src/Orchard/Storage/IStorageFile.cs +++ b/src/Orchard/Storage/IStorageFile.cs @@ -8,6 +8,15 @@ namespace Orchard.Storage { long GetSize(); DateTime GetLastUpdated(); string GetFileType(); - Stream OpenStream(); + + /// + /// Creates a stream for reading from the file. + /// + Stream OpenRead(); + + /// + /// Creates a stream for writing to the file. + /// + Stream OpenWrite(); } } diff --git a/src/Orchard/Storage/IStorageProvider.cs b/src/Orchard/Storage/IStorageProvider.cs index 79b260556..25d4e3eea 100644 --- a/src/Orchard/Storage/IStorageProvider.cs +++ b/src/Orchard/Storage/IStorageProvider.cs @@ -11,5 +11,14 @@ namespace Orchard.Storage { void DeleteFile(string path); void RenameFile(string path, string newPath); IStorageFile CreateFile(string path); + + /// + /// Combines two path strings + /// + /// The first path + /// The second path + /// A string containing the combined paths. If one of the specified paths is a zero-length string, this method returns the other path. + /// If path2 contains an absolute path, this method returns path2. + string Combine(string path1, string path2); } } From 97f8ed294eaf03d5f9699436c3f8a789761f5699 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 26 Apr 2010 11:02:10 -0700 Subject: [PATCH 3/3] Refactored AzureBlobStorage to remove isPrivate Added test on reading/writing content --HG-- branch : dev --- .../Storage/AzureBlobStorageProviderTests.cs | 23 ++++++++++++++++++- .../Storage/AzureBlobStorageProvider.cs | 18 ++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs index b2060dd5e..c9979df6b 100644 --- a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs +++ b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs @@ -7,6 +7,7 @@ using Orchard.Azure.Storage; using Microsoft.WindowsAzure; using System.Linq; using Microsoft.WindowsAzure.StorageClient; +using System.Text; namespace Orchard.Azure.Tests.Storage { [TestFixture] @@ -36,7 +37,7 @@ namespace Orchard.Azure.Tests.Storage { CloudStorageAccount devAccount; CloudStorageAccount.TryParse("UseDevelopmentStorage=true", out devAccount); - _azureBlobStorageProvider = new AzureBlobStorageProvider("default", false, devAccount); + _azureBlobStorageProvider = new AzureBlobStorageProvider("default", devAccount); } [TestFixtureTearDown] @@ -186,5 +187,25 @@ namespace Orchard.Azure.Tests.Storage { Assert.AreEqual(1, _azureBlobStorageProvider.ListFiles("folder1/folder4/folder3").Count()); } + [Test] + public void ShouldReadWriteFiles() { + const string teststring = "This is a test string."; + + var foo = _azureBlobStorageProvider.CreateFile("folder1/foo.txt"); + + using(var stream = foo.OpenWrite()) + using (var writer = new StreamWriter(stream)) + writer.Write(teststring); + + Assert.AreEqual(22, foo.GetSize()); + + string content; + using ( var stream = foo.OpenRead() ) + using ( var reader = new StreamReader(stream) ) { + content = reader.ReadToEnd(); + } + + Assert.AreEqual(teststring, content); + } } } diff --git a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs index 98515b67d..b79fecd88 100644 --- a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs +++ b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs @@ -16,34 +16,24 @@ namespace Orchard.Azure.Storage public CloudBlobClient BlobClient { get; private set; } public CloudBlobContainer Container { get; private set; } - public AzureBlobStorageProvider(string containerName, bool isPrivate) : this(containerName, isPrivate, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) + public AzureBlobStorageProvider(string containerName) : this(containerName, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) { } - public AzureBlobStorageProvider(string containerName, bool isPrivate, CloudStorageAccount storageAccount) + public AzureBlobStorageProvider(string containerName, CloudStorageAccount storageAccount) { // Setup the connection to custom storage accountm, e.g. Development Storage _storageAccount = storageAccount; - InitBlobClient(containerName, isPrivate); - } - private void InitBlobClient(string containerName, bool isPrivate) - { 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); - // Setup the permissions if the container is new - if (Container.CreateIfNotExist()) { - if (isPrivate) - Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Off }); - else - Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); - } + Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); } - private void EnsurePathIsRelative(string path) { + private static void EnsurePathIsRelative(string path) { if(path.StartsWith("/")) throw new ArgumentException("Path must be relative"); }