1+ // Copyright (c) .NET Foundation. All rights reserved.
2+ // Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . IO ;
7+ using System . Linq ;
8+ using System . Reflection ;
9+ using System . Threading ;
10+ using System . Threading . Tasks ;
11+ using Azure . Storage . Blobs ;
12+ using Azure . Storage . Blobs . Specialized ;
13+ using Microsoft . Azure . Functions . Worker . Converters ;
14+ using Microsoft . Azure . Functions . Worker . Core ;
15+ using Microsoft . Extensions . Options ;
16+ using Microsoft . Extensions . Logging ;
17+
18+ namespace Microsoft . Azure . Functions . Worker
19+ {
20+ /// <summary>
21+ /// Converter to bind Blob Storage type parameters.
22+ /// </summary>
23+ internal class BlobStorageConverter : IInputConverter
24+ {
25+ private readonly IOptions < WorkerOptions > _workerOptions ;
26+ private readonly IOptionsSnapshot < BlobStorageBindingOptions > _blobOptions ;
27+
28+ private readonly ILogger < BlobStorageConverter > _logger ;
29+
30+ public BlobStorageConverter ( IOptions < WorkerOptions > workerOptions , IOptionsSnapshot < BlobStorageBindingOptions > blobOptions , ILogger < BlobStorageConverter > logger )
31+ {
32+ _workerOptions = workerOptions ?? throw new ArgumentNullException ( nameof ( workerOptions ) ) ;
33+ _blobOptions = blobOptions ?? throw new ArgumentNullException ( nameof ( blobOptions ) ) ;
34+ _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
35+ }
36+
37+ public async ValueTask < ConversionResult > ConvertAsync ( ConverterContext context )
38+ {
39+ return context ? . Source switch
40+ {
41+ ModelBindingData binding => await ConvertFromBindingDataAsync ( context , binding ) ,
42+ CollectionModelBindingData binding => await ConvertFromCollectionBindingDataAsync ( context , binding ) ,
43+ _ => ConversionResult . Unhandled ( ) ,
44+ } ;
45+ }
46+
47+ private async ValueTask < ConversionResult > ConvertFromBindingDataAsync ( ConverterContext context , ModelBindingData modelBindingData )
48+ {
49+ if ( ! IsBlobExtension ( modelBindingData ) )
50+ {
51+ return ConversionResult . Unhandled ( ) ;
52+ }
53+
54+ try
55+ {
56+ Dictionary < string , string > content = GetBindingDataContent ( modelBindingData ) ;
57+ var result = await ConvertModelBindingDataAsync ( content , context . TargetType , modelBindingData ) ;
58+
59+ if ( result is not null )
60+ {
61+ return ConversionResult . Success ( result ) ;
62+ }
63+ }
64+ catch ( Exception ex )
65+ {
66+ return ConversionResult . Failed ( ex ) ;
67+ }
68+
69+ return ConversionResult . Unhandled ( ) ;
70+ }
71+
72+ private async ValueTask < ConversionResult > ConvertFromCollectionBindingDataAsync ( ConverterContext context , CollectionModelBindingData collectionModelBindingData )
73+ {
74+ var blobCollection = new List < object > ( collectionModelBindingData . ModelBindingDataArray . Length ) ;
75+ Type elementType = context . TargetType . IsArray ? context . TargetType . GetElementType ( ) : context . TargetType . GenericTypeArguments [ 0 ] ;
76+
77+ try
78+ {
79+ foreach ( ModelBindingData modelBindingData in collectionModelBindingData . ModelBindingDataArray )
80+ {
81+ if ( ! IsBlobExtension ( modelBindingData ) )
82+ {
83+ return ConversionResult . Unhandled ( ) ;
84+ }
85+
86+ Dictionary < string , string > content = GetBindingDataContent ( modelBindingData ) ;
87+ var element = await ConvertModelBindingDataAsync ( content , elementType , modelBindingData ) ;
88+
89+ if ( element is not null )
90+ {
91+ blobCollection . Add ( element ) ;
92+ }
93+ }
94+
95+ var methodName = context . TargetType . IsArray ? nameof ( CloneToArray ) : nameof ( CloneToList ) ;
96+ var result = ToTargetTypeCollection ( blobCollection , methodName , elementType ) ;
97+
98+ return ConversionResult . Success ( result ) ;
99+ }
100+ catch ( Exception ex )
101+ {
102+ return ConversionResult . Failed ( ex ) ;
103+ }
104+ }
105+
106+ private bool IsBlobExtension ( ModelBindingData bindingData )
107+ {
108+ if ( bindingData ? . Source is not Constants . BlobExtensionName )
109+ {
110+ _logger . LogTrace ( "Source '{source}' is not supported by {converter}" , bindingData ? . Source , nameof ( BlobStorageConverter ) ) ;
111+ return false ;
112+ }
113+
114+ return true ;
115+ }
116+
117+ private Dictionary < string , string > GetBindingDataContent ( ModelBindingData bindingData )
118+ {
119+ return bindingData ? . ContentType switch
120+ {
121+ Constants . JsonContentType => new Dictionary < string , string > ( bindingData ? . Content ? . ToObjectFromJson < Dictionary < string , string > > ( ) , StringComparer . OrdinalIgnoreCase ) ,
122+ _ => throw new NotSupportedException ( $ "Unexpected content-type. Currently only { Constants . JsonContentType } is supported.")
123+ } ;
124+ }
125+
126+ private async Task < object ? > ConvertModelBindingDataAsync ( IDictionary < string , string > content , Type targetType , ModelBindingData bindingData )
127+ {
128+ content . TryGetValue ( Constants . Connection , out var connectionName ) ;
129+ content . TryGetValue ( Constants . ContainerName , out var containerName ) ;
130+ content . TryGetValue ( Constants . BlobName , out var blobName ) ;
131+
132+ if ( string . IsNullOrEmpty ( connectionName ) || string . IsNullOrEmpty ( containerName ) )
133+ {
134+ throw new ArgumentNullException ( "'Connection' and 'ContainerName' cannot be null or empty" ) ;
135+ }
136+
137+ return await ToTargetTypeAsync ( targetType , connectionName , containerName , blobName ) ;
138+ }
139+
140+ private async Task < object ? > ToTargetTypeAsync ( Type targetType , string connectionName , string containerName , string blobName ) => targetType switch
141+ {
142+ Type _ when targetType == typeof ( String ) => await GetBlobStringAsync ( connectionName , containerName , blobName ) ,
143+ Type _ when targetType == typeof ( Stream ) => await GetBlobStreamAsync ( connectionName , containerName , blobName ) ,
144+ Type _ when targetType == typeof ( Byte [ ] ) => await GetBlobBinaryDataAsync ( connectionName , containerName , blobName ) ,
145+ Type _ when targetType == typeof ( BlobBaseClient ) => CreateBlobClient < BlobBaseClient > ( connectionName , containerName , blobName ) ,
146+ Type _ when targetType == typeof ( BlobClient ) => CreateBlobClient < BlobClient > ( connectionName , containerName , blobName ) ,
147+ Type _ when targetType == typeof ( BlockBlobClient ) => CreateBlobClient < BlockBlobClient > ( connectionName , containerName , blobName ) ,
148+ Type _ when targetType == typeof ( PageBlobClient ) => CreateBlobClient < PageBlobClient > ( connectionName , containerName , blobName ) ,
149+ Type _ when targetType == typeof ( AppendBlobClient ) => CreateBlobClient < AppendBlobClient > ( connectionName , containerName , blobName ) ,
150+ Type _ when targetType == typeof ( BlobContainerClient ) => CreateBlobContainerClient ( connectionName , containerName ) ,
151+ _ => await DeserializeToTargetObjectAsync ( targetType , connectionName , containerName , blobName )
152+ } ;
153+
154+ private async Task < object ? > DeserializeToTargetObjectAsync ( Type targetType , string connectionName , string containerName , string blobName )
155+ {
156+ var content = await GetBlobStreamAsync ( connectionName , containerName , blobName ) ;
157+ return _workerOptions ? . Value ? . Serializer ? . Deserialize ( content , targetType , CancellationToken . None ) ;
158+ }
159+
160+ private object ? ToTargetTypeCollection ( IEnumerable < object > blobCollection , string methodName , Type type )
161+ {
162+ blobCollection = blobCollection . Select ( b => Convert . ChangeType ( b , type ) ) ;
163+ MethodInfo method = typeof ( BlobStorageConverter ) . GetMethod ( methodName , BindingFlags . Static | BindingFlags . NonPublic ) ;
164+ MethodInfo genericMethod = method . MakeGenericMethod ( type ) ;
165+
166+ return genericMethod . Invoke ( null , new [ ] { blobCollection . ToList ( ) } ) ;
167+ }
168+
169+ private static T [ ] CloneToArray < T > ( IList < object > source )
170+ {
171+ return source . Cast < T > ( ) . ToArray ( ) ;
172+ }
173+
174+ private static IEnumerable < T > CloneToList < T > ( IList < object > source )
175+ {
176+ return source . Cast < T > ( ) ;
177+ }
178+
179+ private async Task < string > GetBlobStringAsync ( string connectionName , string containerName , string blobName )
180+ {
181+ var client = CreateBlobClient < BlobClient > ( connectionName , containerName , blobName ) ;
182+ return await GetBlobContentStringAsync ( client ) ;
183+ }
184+
185+ private async Task < string > GetBlobContentStringAsync ( BlobClient client )
186+ {
187+ var download = await client . DownloadContentAsync ( ) ;
188+ return download . Value . Content . ToString ( ) ;
189+ }
190+
191+ private async Task < Byte [ ] > GetBlobBinaryDataAsync ( string connectionName , string containerName , string blobName )
192+ {
193+ using MemoryStream stream = new ( ) ;
194+ var client = CreateBlobClient < BlobClient > ( connectionName , containerName , blobName ) ;
195+ await client . DownloadToAsync ( stream ) ;
196+ return stream . ToArray ( ) ;
197+ }
198+
199+ private async Task < Stream > GetBlobStreamAsync ( string connectionName , string containerName , string blobName )
200+ {
201+ var client = CreateBlobClient < BlobClient > ( connectionName , containerName , blobName ) ;
202+ var download = await client . DownloadStreamingAsync ( ) ;
203+ return download . Value . Content ;
204+ }
205+
206+ private BlobContainerClient CreateBlobContainerClient ( string connectionName , string containerName )
207+ {
208+ var blobStorageOptions = _blobOptions . Get ( connectionName ) ;
209+ BlobServiceClient blobServiceClient = blobStorageOptions . CreateClient ( ) ;
210+ BlobContainerClient container = blobServiceClient . GetBlobContainerClient ( containerName ) ;
211+ return container ;
212+ }
213+
214+ private T CreateBlobClient < T > ( string connectionName , string containerName , string blobName ) where T : BlobBaseClient
215+ {
216+ if ( string . IsNullOrEmpty ( blobName ) )
217+ {
218+ throw new ArgumentNullException ( nameof ( blobName ) ) ;
219+ }
220+
221+ BlobContainerClient container = CreateBlobContainerClient ( connectionName , containerName ) ;
222+
223+ Type targetType = typeof ( T ) ;
224+ BlobBaseClient blobClient = targetType switch
225+ {
226+ Type _ when targetType == typeof ( BlobClient ) => container . GetBlobClient ( blobName ) ,
227+ Type _ when targetType == typeof ( BlockBlobClient ) => container . GetBlockBlobClient ( blobName ) ,
228+ Type _ when targetType == typeof ( PageBlobClient ) => container . GetPageBlobClient ( blobName ) ,
229+ Type _ when targetType == typeof ( AppendBlobClient ) => container . GetAppendBlobClient ( blobName ) ,
230+ _ => container . GetBlobBaseClient ( blobName )
231+ } ;
232+
233+ return ( T ) blobClient ;
234+ }
235+ }
236+ }
0 commit comments