C# 如何重构代码以将HTTP触发器转换为Blob触发器?

C# 如何重构代码以将HTTP触发器转换为Blob触发器?,c#,.net-core,azure-functions,azure-blob-storage,C#,.net Core,Azure Functions,Azure Blob Storage,我有这个功能,基本上上传视频到YouTube。但是,目前我必须对视频所在的实际文件路径进行硬编码(例如:@“C:\Users\Peter\Desktop\audio\test.mp4”)。我想知道有没有一种方法可以让它更有活力。例如,现在我正在使用HTTP触发器,但是如何重构代码使其成为Blob触发器呢?因此,当我将一个新的.mp4文件上传到blob存储容器中时,就会触发此函数 我这样问是因为,我计划将此功能移动到Azure portal,而在那里,我将无法像现在这样指定本地文件路径。提前谢谢

我有这个功能,基本上上传视频到YouTube。但是,目前我必须对视频所在的实际文件路径进行硬编码(例如:@“C:\Users\Peter\Desktop\audio\test.mp4”)。我想知道有没有一种方法可以让它更有活力。例如,现在我正在使用HTTP触发器,但是如何重构代码使其成为Blob触发器呢?因此,当我将一个新的.mp4文件上传到blob存储容器中时,就会触发此函数

我这样问是因为,我计划将此功能移动到Azure portal,而在那里,我将无法像现在这样指定本地文件路径。提前谢谢

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3.Data;
using System.Reflection;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using System.Threading;

namespace UploadVideoBlob
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task Run([BlobTrigger("video/{name}")]Stream myBlob, string name, Microsoft.Azure.WebJobs.ExecutionContext context, ILogger log)
        {
            UserCredential credential;
            using(var stream = new FileStream(System.IO.Path.Combine(context.FunctionDirectory, "client_secrets.json"), FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { YouTubeService.Scope.YoutubeUpload },
                    "user",
                    CancellationToken.None
                );
            }

            var youtubeService = new YouTubeService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
            });

            var video = new Video();
            video.Snippet = new VideoSnippet();
            video.Snippet.Title = "Default Video Title";
            video.Snippet.Description = "Default Video Description";
            video.Snippet.Tags = new string[] { "tag1", "tag2" };
            video.Snippet.CategoryId = "22";
            video.Status = new VideoStatus();
            video.Status.PrivacyStatus = "unlisted";
            var VideoInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", myBlob, "video/*");
            await VideoInsertRequest.UploadAsync();
        }
    }
}
function.json

{
  "generatedBy": "Microsoft.NET.Sdk.Functions-1.0.29",
  "configurationSource": "attributes",
  "bindings": [
    {
      "type": "blobTrigger",
      "path": "video/{name}",
      "name": "myBlob"
    }
  ],
  "disabled": false,
  "scriptFile": "../bin/UploadVideoBlob.dll",
  "entryPoint": "UploadVideoBlob.Function1.Run"
}
client_secrets.json

{
  "installed": {
    "client_id": "147300761218-dl0rhktkoj8arh0ebu5pu56es06hje5p.apps.googleusercontent.com",
    "project_id": "mytestproj",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "xxxxxxxxxxxxxxxxxx",
    "redirect_uris": [ "urn:ietf:wg:oauth:2.0:oob"]
  }
}

这是一个相当直接的变化。只要换上Blob触发器。替换:

public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
这将为您提供一个要处理的流(videoBlob)(以及视频文件名,如果您需要)。然后用这个新流替换文件流。您似乎使用了Google/YouTube示例代码来构造函数,但不太需要为Azure函数创建单独的Run()方法。您可以简化操作,但可以将Run()方法合并到主函数代码中,而不是调用“wait Run();”

将您的功能更改为:

using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;


namespace UploadVideoBlob
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task Run([BlobTrigger("video/{name}", 
            Connection = "DefaultEndpointsProtocol=https;AccountName=uploadvideoblob;AccountKey=XXXX;EndpointSuffix=core.windows.net")]Stream videoBlob, string name,
            Microsoft.Azure.WebJobs.ExecutionContext context, ILogger log)
        {
            UserCredential credential;
            using (var stream = new FileStream(System.IO.Path.Combine(context.FunctionDirectory, "client_secrets.json"), FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    // This OAuth 2.0 access scope allows an application to upload files to the
                    // authenticated user's YouTube channel, but doesn't allow other types of access.
                    new[] { YouTubeService.Scope.YoutubeUpload },
                    "user",
                    CancellationToken.None
                );
            }

            var youtubeService = new YouTubeService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
            });

            var video = new Video();
            video.Snippet = new VideoSnippet();
            video.Snippet.Title = "Default Video Title";
            video.Snippet.Description = "Default Video Description";
            video.Snippet.Tags = new string[] { "tag1", "tag2" };
            video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
            video.Status = new VideoStatus();
            video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
            var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", videoBlob, "video/*");
            await videosInsertRequest.UploadAsync();
        }
    }
}
除了日志记录之外,函数实际上不需要使用ProgressChanged和ResponseReceived事件,因此您可以保留或删除它们。谷歌的例子是一个控制台应用程序,因此它向控制台输出大量状态


我还做了一个额外的更改来更正“client_secrets.json”的文件路径。此代码假定json文件与您的函数位于同一目录中,并随函数一起发布。

我有一些空闲时间,尽管使用Azure函数已经一年多了,但从未有机会实现实际的
BlobTrigger
。所以我想我应该分享我的微型核心3.0测试实现,并添加一个过度的答案作为@Bryan Lewis正确答案的补充

如果您想在Azure上启动之前测试此功能,应首先确保您拥有Azure存储模拟器。如果您有Visual Studio 2019,则应该已经安装了它。如果您有VS19但尚未安装,则应打开Visual Studio安装程序并修改VS19安装。在“单个组件”下,您应该能够找到“Azure存储模拟器”。如果你没有VS19,你可以得到它

接下来,我建议您从下载Azure Storage Explorer。如果仿真器正在运行,并且您没有更改存储仿真器的默认端口,那么您应该能够在本地&附加>存储帐户>(仿真器-默认端口)下找到一个默认条目

使用存储资源管理器,可以展开“Blob容器”。右键单击“Blob容器”,选择“创建Blob容器”,并为其命名。在我的例子中,我把它命名为“youtube文件”。我还创建了另一个容器,称之为“youtube文件描述”

现在是实际函数。我给了自己使用依赖注入的自由(我只是害怕静态混乱)。为此,您必须包括NuGet软件包Microsoft.Azure.Functions.Extensions和Microsoft.Extensions.DependencyInjection

启动
我们在这里注册我们的服务。我将添加一个InternalYoutubeService(命名为InternalYoutubeService以避免与GoodleAPI提供的服务混淆)。您可以阅读有关DI和Azure函数的更多信息

YoutubeService
将包含处理实际身份验证(您正在使用的OAuth2)和文件上载的逻辑。关于如何使用传入的
,您可以参考@Bryan Lewis的答案。我们可以将凭证存储在函数应用程序的配置中,并插入IConfiguration接口,该接口允许我们通过提供配置中定义的值的键来访问这些值。这样可以避免在代码中硬编码任何凭据。我省略了YouTube特定的上传逻辑,因为我对您使用的库没有经验,但它应该足够简单,可以将逻辑迁移到服务中

public interface IInternalYoutubeService
{
    Task UploadVideo(Stream stream, string title, string description, params string[] tags);
    Task UploadAudio(Stream stream, string title, string description, params string[] tags);
}

internal class InternalYoutubeService : IInternalYoutubeService
{
    private readonly IConfiguration _Configuration;
    private readonly ILogger _Logger;

    public InternalYoutubeService(IConfiguration configuration, ILogger<InternalYoutubeService> logger)
    {
        _Configuration = configuration;
        _Logger = logger;
    }

    public async Task UploadAudio(Stream stream, string title, string description, params string[] tags)
    {
        _Logger.LogInformation($"{_Configuration["YoutubeAccountName"]}");
        _Logger.LogInformation($"{_Configuration["YoutubeAccountPass"]}");
        _Logger.LogInformation($"Bytes: {stream.Length} - {title} - {description} - {string.Join(", ", tags)}");
    }

    public async Task UploadVideo(Stream stream, string title, string description, params string[] tags)
    {
        _Logger.LogInformation($"{_Configuration["YoutubeAccountName"]}");
        _Logger.LogInformation($"{_Configuration["YoutubeAccountPass"]}");
        _Logger.LogInformation($"Bytes: {stream.Length} - {title} - {description} - {string.Join(", ", tags)}");
    }
}
测试
我正在使用一个简单的文本文件“testfiledescription.txt”进行测试,其中包含文本“thisasampledescription.”。我还有一个~5MB的MP3文件,“Test file.MP3”。我首先将文本文件拖放到“youtube文件描述”容器中,然后将“Test file.mp3”文件拖放到“youtube文件”容器中。上传文本文件不会触发该功能;直到我上传“testfile.mp3”后,函数才会触发。我看到以下行被记录:

Executing 'Function1' (Reason='New blob detected: youtube-files/Test File.mp3', Id=50a50657-a9bb-41a5-a7d5-2adb84477f69)
MyAccountName
MySecretPassword
Bytes: 5065849 - Test File - This is a sample description. -
Executed 'Function1' (Succeeded, Id=50a50657-a9bb-41a5-a7d5-2adb84477f69)

嗨@Bryan Lewis,有一件事我仍然很困惑,那就是如何处理blob存储的“文件路径”?例如,如果我在博客存储容器中添加了一个新的.mp4,如何将该文件作为输入参数传递到函数中?目前,正如您所知,我已经硬编码了文件路径,只是为了在本地计算机上测试它。但是当我将该功能移动到Azure门户时,我将无法使用硬编码方式。您根本没有使用文件路径。您正在删除filePath变量和FileStream using语句,因为blob触发器“提供”了一个流对象供您使用。该流是blob存储中的文件。我更新了我的答案以澄清问题。需要测试的一个重要问题是文件大小和函数超时。如果您的视频很大(数千GB),那么您需要意识到您正在将blob从blob存储复制到Azure功能VM,然后将其上载到YouTube。所有这些复制都需要时间,Azure函数在消费计划上有10分钟的超时。所以,如果您有大文件,请进行测试。在本地计算机上使用Azure函数运行时可能不会遇到这些问题
[StorageAccount("AzureWebJobsStorage")]
public class BlobFunction
{
    private readonly IInternalYoutubeService _YoutubeService;
    private readonly ILogger _Logger;

    // We inject the YoutubeService    
    public BlobFunction(IInternalYoutubeService youtubeService, ILogger<BlobFunction> logger)
    {
        _YoutubeService = youtubeService;
        _Logger = logger;
    }

    [FunctionName("Function1")]
    public async Task Run(
        [BlobTrigger("youtube-files/{filename}.{extension}")] Stream blob,
        [Blob("youtube-files-descriptions/{filename}-description.txt")] string description,
        string filename,
        string extension)
    {
        switch (extension)
        {
            case "mp4":
                await _YoutubeService.UploadVideo(blob, filename, description, "Some tag", "Another tag", "An awesome tag");
                break;

            case "mp3":
                await _YoutubeService.UploadAudio(blob, filename, description);
                break;

            default:
                _Logger.LogInformation($"{filename}.{extension} not handled");
                break;
        }
    }
}
public interface IInternalYoutubeService
{
    Task UploadVideo(Stream stream, string title, string description, params string[] tags);
    Task UploadAudio(Stream stream, string title, string description, params string[] tags);
}

internal class InternalYoutubeService : IInternalYoutubeService
{
    private readonly IConfiguration _Configuration;
    private readonly ILogger _Logger;

    public InternalYoutubeService(IConfiguration configuration, ILogger<InternalYoutubeService> logger)
    {
        _Configuration = configuration;
        _Logger = logger;
    }

    public async Task UploadAudio(Stream stream, string title, string description, params string[] tags)
    {
        _Logger.LogInformation($"{_Configuration["YoutubeAccountName"]}");
        _Logger.LogInformation($"{_Configuration["YoutubeAccountPass"]}");
        _Logger.LogInformation($"Bytes: {stream.Length} - {title} - {description} - {string.Join(", ", tags)}");
    }

    public async Task UploadVideo(Stream stream, string title, string description, params string[] tags)
    {
        _Logger.LogInformation($"{_Configuration["YoutubeAccountName"]}");
        _Logger.LogInformation($"{_Configuration["YoutubeAccountPass"]}");
        _Logger.LogInformation($"Bytes: {stream.Length} - {title} - {description} - {string.Join(", ", tags)}");
    }
}
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "YoutubeAccountName": "MyAccountName",
    "YoutubeAccountPass": "MySecretPassword"
  }
}
Executing 'Function1' (Reason='New blob detected: youtube-files/Test File.mp3', Id=50a50657-a9bb-41a5-a7d5-2adb84477f69)
MyAccountName
MySecretPassword
Bytes: 5065849 - Test File - This is a sample description. -
Executed 'Function1' (Succeeded, Id=50a50657-a9bb-41a5-a7d5-2adb84477f69)