1
votes

I'm attempting to create C# Azure WebJob which is triggered on a new Blob creation to resize the uploaded image into three different sizes. I found and followed this great tutorial.

There are two sections, the first portion "works" but enters into a recursion loop as the creation of the three new sizes triggers the script, which creates three more instances for each of three new images, so forth and so forth. This was intentional, to highlight the need for the final implementation.

Here is the initial recursion loop code which "works" location in the Functions.cs file:

public static void ResizeImagesW800([BlobTrigger("input/{name}.{ext}")] Stream input,
    [Blob("output/{name}-w800.{ext}", FileAccess.Write)] Stream output)
{
    ResizeImage(input, output, 800);
}

public static void ResizeImagesW500([BlobTrigger("input/{name}.{ext}")] Stream input,
    [Blob("output/{name}-w500.{ext}", FileAccess.Write)] Stream output)
{
    ResizeImage(input, output, 500);
}

private static void ResizeImage(Stream input, Stream output, int width)
{
    var instructions = new Instructions
    {
        Width = width,
        Mode = FitMode.Carve,
        Scale = ScaleMode.Both
    };
    ImageBuilder.Current.Build(new ImageJob(input, output, instructions));
}

Here is the code which Visual Studio 2015 gives an error on:

public static void ResizeImagesTask(
    [BlobTrigger("input/{name}.{ext}")] Stream inputBlob,
    string name,
    string ext,
    IBinder binder)
{
    int[] sizes = { 800, 500, 250 };
    var inputBytes = inputBlob.CopyToBytes();
    foreach (var width in sizes)
    {
        var input = new MemoryStream(inputBytes);
        var output = binder.Bind<Stream>(new BlobAttribute($"output/{name}-w{width}.{ext}", FileAccess.Write));

        ResizeImage(input, output, width);
    }
}

private static void ResizeImage(Stream input, Stream output, int width)
{
    var instructions = new Instructions
    {
        Width = width,
        Mode = FitMode.Carve,
        Scale = ScaleMode.Both
    };
    ImageBuilder.Current.Build(new ImageJob(input, output, instructions));
}

The error is thrown at this line:

 var inputBytes = inputBlob.CopyToBytes();

The error is:

CS1061: 'Stream' does not contain a definition for 'CopyToBytes' and no extension method 'CopyToBytes' accepting a first argument of type 'Stream' could be found (are you missing a using directive or an assembly reference?)

I've tried using .NET 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1 as target frameworks, but all of them throw the same error.

Also, here are the using statements for the Functions.cs file:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.WindowsAzure.Storage;
using ImageResizer;

What am I doing wrong here? Thanks!

UPDATE 1

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.WindowsAzure.Storage;
using ImageResizer;
using ImageResizer.ExtensionMethods;
using Microsoft.WindowsAzure.Storage.Blob;

namespace HilcoIndustrialAssetApiWebJob
{
    public class Functions
    {
        // output blolb sizes
        private static readonly int[] Sizes = { 800, 500, 250 };

        public static void ResizeImagesTask(
        [QueueTrigger("newfileuploaded")] string filename,
        [Blob("input/{queueTrigger}", FileAccess.Read)] Stream blobStream,
        [Blob("output")] CloudBlobContainer container)
        {
            // Extract the filename  and the file extension
            var name = Path.GetFileNameWithoutExtension(filename);
            var ext = Path.GetExtension(filename);

            Console.WriteLine("New Blob name -> " + name);

            // Get the mime type to set the content type
            var mimeType = MimeMapping.GetMimeMapping(filename);

            foreach (var width in Sizes)
            {
                // Set the position of the input stream to the beginning.
                blobStream.Seek(0, SeekOrigin.Begin);

                // Get the output stream
                var outputStream = new MemoryStream();
                ResizeImage(blobStream, outputStream, width);

                // Get the blob reference
                var blob = container.GetBlockBlobReference($"{name}_{width}.{ext}");

                // Set the position of the output stream to the beginning.
                outputStream.Seek(0, SeekOrigin.Begin);
                blob.UploadFromStream(outputStream);

                // Update the content type =>  don't know if required
                blob.Properties.ContentType = mimeType;
                blob.SetProperties();
            }
        }

        private static void ResizeImage(Stream input, Stream output, int width)
        {
            var instructions = new Instructions
            {
                Width = width,
                Mode = FitMode.Carve,
                Scale = ScaleMode.Both
            };
            var imageJob = new ImageJob(input, output, instructions);

            // Do not dispose the source object
            imageJob.DisposeSourceObject = false;
            imageJob.Build();
        }
    }
}
3
Have you seen this post stackoverflow.com/questions/7238018/… ?Thomas
Brian, I've edited your question to add tag to azure blob storageThomas
Brian, would you like to rename the title of your post to something like that : Azure WebJobs Blob Trigger - multiple resizes ?Thomas
@Thomas, I had written another question specifically for this, but changed title as sugestedBrian Bolli

3 Answers

2
votes

I guess the sample use ImageResizer NuGet package. You may install it from VS2015 with the command Install-Package ImageResizer. Then if you add using ImageResizer.ExtensionMethods; in your code, you'll get the CopyToBytes method extending the Stream object. Hope this helps Best regards Stéphane

0
votes

Azure Webjob SDK support Blob Binding so that you can bind directly to a blob.

In your context, you want to bind to an input blob and create multiple output blobs.

  • Use the BlobTriggerAttribute for the input.
  • Use the BlobTriggerAttribute to bind to your output blobs. Because you want to create multiple outputs blobs, you can bind directly to the output container.

The code of your triggered function can look like that:

using System.IO;
using System.Web;
using ImageResizer;
using Microsoft.Azure.WebJobs;
using Microsoft.WindowsAzure.Storage.Blob;

public class Functions
{
    // output blolb sizes
    private static readonly int[] Sizes = {800, 500, 250};

    public static void ResizeImage(
        [BlobTrigger("input/{name}.{ext}")] Stream blobStream, string name, string ext
        , [Blob("output")] CloudBlobContainer container)
    {
        // Get the mime type to set the content type
        var mimeType = MimeMapping.GetMimeMapping($"{name}.{ext}");
        foreach (var width in Sizes)
        {
            // Set the position of the input stream to the beginning.
            blobStream.Seek(0, SeekOrigin.Begin);

            // Get the output stream
            var outputStream = new MemoryStream();
            ResizeImage(blobStream, outputStream, width);

            // Get the blob reference
            var blob = container.GetBlockBlobReference($"{name}-w{width}.{ext}");

            // Set the position of the output stream to the beginning.
            outputStream.Seek(0, SeekOrigin.Begin);
            blob.UploadFromStream(outputStream);

            // Update the content type
            blob.Properties.ContentType = mimeType;
            blob.SetProperties();
        }
    }

    private static void ResizeImage(Stream input, Stream output, int width)
    {
        var instructions = new Instructions
        {
            Width = width,
            Mode = FitMode.Carve,
            Scale = ScaleMode.Both
        };
        var imageJob = new ImageJob(input, output, instructions);

        // Do not dispose the source object
        imageJob.DisposeSourceObject = false;
        imageJob.Build();
    }
}

Note the use of the DisposeSourceObject on the ImageJob object so that we can read multiple time the blob stream.

In addition, you should have a look at the Webjob documentation about BlobTrigger: How to use Azure blob storage with the WebJobs SDK

The WebJobs SDK scans log files to watch for new or changed blobs. This process is not real-time; a function might not get triggered until several minutes or longer after the blob is created. In addition, storage logs are created on a "best efforts" basis; there is no guarantee that all events will be captured. Under some conditions, logs might be missed. If the speed and reliability limitations of blob triggers are not acceptable for your application, the recommended method is to create a queue message when you create the blob, and use the QueueTrigger attribute instead of the BlobTrigger attribute on the function that processes the blob.

So it could be better to trigger message from a queue that just send the filename, you can bind automatically the input blob to the message queue :

using System.IO;
using System.Web;
using ImageResizer;
using Microsoft.Azure.WebJobs;
using Microsoft.WindowsAzure.Storage.Blob;

public class Functions
{
    // output blolb sizes
    private static readonly int[] Sizes = { 800, 500, 250 };

    public static void ResizeImagesTask1(
        [QueueTrigger("newfileuploaded")] string filename,
        [Blob("input/{queueTrigger}", FileAccess.Read)] Stream blobStream,
        [Blob("output")] CloudBlobContainer container)
    {
        // Extract the filename  and the file extension
        var name = Path.GetFileNameWithoutExtension(filename);
        var ext = Path.GetExtension(filename);

        // Get the mime type to set the content type
        var mimeType = MimeMapping.GetMimeMapping(filename);

        foreach (var width in Sizes)
        {
            // Set the position of the input stream to the beginning.
            blobStream.Seek(0, SeekOrigin.Begin);

            // Get the output stream
            var outputStream = new MemoryStream();
            ResizeImage(blobStream, outputStream, width);

            // Get the blob reference
            var blob = container.GetBlockBlobReference($"{name}-w{width}.{ext}");

            // Set the position of the output stream to the beginning.
            outputStream.Seek(0, SeekOrigin.Begin);
            blob.UploadFromStream(outputStream);

            // Update the content type =>  don't know if required
            blob.Properties.ContentType = mimeType;
            blob.SetProperties();
        }
    }

    private static void ResizeImage(Stream input, Stream output, int width)
    {
        var instructions = new Instructions
        {
            Width = width,
            Mode = FitMode.Carve,
            Scale = ScaleMode.Both
        };
        var imageJob = new ImageJob(input, output, instructions);

        // Do not dispose the source object
        imageJob.DisposeSourceObject = false;
        imageJob.Build();
    }
}
0
votes

I have created a Webjob project with VS2015 (still in update1), I'm using Azure 2.8 SDK (I will soon update it to 2.9).

I have done a cut & paste with the sample code from your original link. I have added the two app config connection strings (and updated the Web App corresponding connection strings in Application Settings). I have juste added the referenced nugget package and the missing "using". It works fine.

I have published the sample code on GitHub in case you wish to try it.

https://github.com/stephgou/ImageResizer.git

You just have to update the app config and your web app connection strings.

Hope this helps

Best regards Stéphane