locked
How to upload a photo to Blob from a a WCF Service Web Role? RRS feed

  • Question

  • First of all let me say that I have been through the Hands-On Labs "Introduction to Windows Azure" and Exploring Windows Azure Storage".

    I am trying to upload a photo to blob storage which is coming into the WCF Service Web Role from an ASP.NET MVC client application. Below I will paste the different parts of my code so you can get a picture of the scenario.

    In the ASP.NET MVC client the code looks like this:

    [HttpPost]
        public ActionResult Index(HttpPostedFileBase file)
        {
          if (file.ContentLength > 0)
          {
            using (ServiceReference1.SHAServiceClient myWs = new ServiceReference1.SHAServiceClient()) 
            using (var reader = new BinaryReader(file.InputStream)) 
            {
              var contentType = file.ContentType;
              var binData = reader.ReadBytes(file.ContentLength); 
              myWs.SaveImage(contentType, binData); 
            }
          }
          return RedirectToAction("Index");
        }
    

    In the SHAService.svc.cs class of the Service Role the code looks like this:

     public class SHAService : ISHAService
      {
        public void SaveImage(string contentType, byte[] photo)
        {      
          this.SaveImageAsBlob
          (
            Guid.NewGuid().ToString(),
            contentType,
            photo
          );
        }
    
    #region ProcessImage
    
        // Create a blob in container and upload image bytes to it
        private void SaveImageAsBlob(string name, string contentType, byte[] data)
        {
          this.EnsureContainerExists();
    
          // Gets a reference to a blob with a particular name in the CloudBlobContainer
          var blob = this.GetContainer().GetBlobReference(name);
    
          // Set the content-type value for the blob.
          blob.Properties.ContentType = contentType;
    
          // Upload the array of bytes to the blob.
          blob.UploadByteArray(data);
        }
    
        private void EnsureContainerExists()
        {
          var container = GetContainer();
          container.CreateIfNotExist();
    
          var permissions = container.GetPermissions();
          permissions.PublicAccess = BlobContainerPublicAccessType.Container;
          container.SetPermissions(permissions);
        }
    
        private CloudBlobContainer GetContainer()
        {
          // Get a handle on account, create a blob service client and get container proxy
          var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
          var client = account.CreateCloudBlobClient();
    
          return client.GetContainerReference(RoleEnvironment.GetConfigurationSettingValue("ContainerName"));
        }
    #endregion    
      }
    

    In the WebRole.cs file of the Service Role the code looks like this:

        public override bool OnStart()
        {
          // 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));
          });
    
          return base.OnStart();
         }
    

    Finally in the ServiceConfiguration.cscfg file of the Service Role the code looks like this:

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceConfiguration serviceName="SHAService" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="1" osVersion="*">
     <Role name="SHAServiceRole">
      <Instances count="1" />
      <ConfigurationSettings>
       <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
       <Setting name="DataConnectionString" value="UseDevelopmentStorage=true" />
       <Setting name="ContainerName" value="sixapril" />
      </ConfigurationSettings>
     </Role>
    </ServiceConfiguration>
    

    The Web.config of the Service Role looks like this:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.diagnostics>
        <trace>
          <listeners>
            <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">
              <filter type="" />
            </add>
          </listeners>
        </trace>
      </system.diagnostics>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <machineKey decryption="AES" decryptionKey="0CA3EFAF0F7A5E7A62681C0BF656EE0ECE31ACEE3E1023BA3FAD20EA5F199DE8" validation="SHA1" validationKey="1F4630DAF454F3643CD37D68E474A4999D1BC3959DE62168764FF0DCE537184F0535D5D9AD66DEDC97DC1ABFF7FA540B4DFD82E5BB196B95D15FF81F75AD5328" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding maxReceivedMessageSize="1000000000">
              <readerQuotas maxStringContentLength="5242880" maxArrayLength="1000000000" />
            </binding>
          </basicHttpBinding>
        </bindings>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
              <serviceMetadata httpGetEnabled="true" />
              <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
      </system.webServer>
    </configuration>
    

    When I try to run the code I get error in both the Service Role and the ASP.NET MVC client.

    The error in the Service Role is method GetContainer() class SHAService.svc.cs, it sounds like this:

    InvalidOperationExceptiion was unhandled by user code - SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used.

    The error in the client is in occurs when the controller calls the web service method it sounds like this:

    FaultException was unhandled by user code - The server was unable to process the request due to an internal error.

    How can I make it work?


    rune007
    Thursday, April 7, 2011 2:46 PM

Answers

  • I guess, fault exception in your MVC client could be because of exception in your service role. If you are using Azure SDK 1.3 or later, follow instructions in this blog to fix the issue in Service role.

    HTH.


    Please mark it as answer by clicking on "Propose As Answer", if it helps. My Blog : http://dotnetizen.blogspot.com
    • Marked as answer by rune007 Thursday, April 7, 2011 4:17 PM
    Thursday, April 7, 2011 3:28 PM

All replies

  • Are you aware that you could upload directly to Windows Azure blob storage without the need to go through a WCF service first?  To control write access, you can use a Shared Access Signature with a short time to live that would allow the client with the signature to write directly to your blob storage.  You would save the overhead of first going through the WCF service, and then going through the REST service for writing to the Windows Azure container.

    You can find some info at http://blog.smarx.com/posts/shared-access-signatures-are-easy-these-days and also on MSDN at http://msdn.microsoft.com/en-us/library/ee395415.aspx.

     

    Thursday, April 7, 2011 3:10 PM
  • I guess, fault exception in your MVC client could be because of exception in your service role. If you are using Azure SDK 1.3 or later, follow instructions in this blog to fix the issue in Service role.

    HTH.


    Please mark it as answer by clicking on "Propose As Answer", if it helps. My Blog : http://dotnetizen.blogspot.com
    • Marked as answer by rune007 Thursday, April 7, 2011 4:17 PM
    Thursday, April 7, 2011 3:28 PM
  • Hi Rajesh Kolla

    Your answer solved my problem, I added a Global.asax file to the service role and put this code in the Application_Start() method:

        protected void Application_Start(object sender, EventArgs e)
        {
          // 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));
          });
        }
    

    Thank you Mr. Rajesh Kolla!

    Also thank you to Mr. Michael Collier, this Shared Access Signature sounds interesting, but now that Mr. Rajesh Kolla have solved my issue I will press on with this solution and try to make it work for me.

    My project involves 3 kind of clients: ASP.NET MVC, WP7 and WPF, so I was a bit worried if this Shared Access Signature could turn more complex if I had to write it asynchronously in the Silverlight clients. But thank you for your attention Mr. Michael Collier!

    Now the last thing, I might need to ask this in a separate thread,  I am going to retrieve the URI for the blobs through the blob.Uri property. But I have noticed that uploading photo to blob appeared to be much slower than uploading photo to file on the C drive. So I was just wondering if one needs to delay the application on some Thread.Sleep() or something before one can get the Uri? I am hoping to store the Uri in the database and then I hope my clients can display the photos stored in blob knowing the Uri. Thank you Guys!


    rune007
    Thursday, April 7, 2011 4:17 PM