Functions — Input/Output Bindings for Simplified Data Access
Overview
Azure Functions bindings provide a declarative way to connect to Azure services without writing integration code. Input bindings let you read data into your function, and output bindings let you write data out. This dramatically simplifies your code - instead of managing connections and SDKs, you just work with parameters.
What You'll Learn
- Using input bindings to read from various services
- Using output bindings to write to services
- Binding expressions and configuration
- Common binding patterns
Problem
Your functions contain:
- Boilerplate code to connect to services
- SDK initialization and connection management
- Repetitive error handling for service calls
Solution - Input Bindings
Blob Input Binding
public static class BlobInputFunction
{
[FunctionName("ReadBlob")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[Blob("container/{filename}")] string blobContent,
ILogger log)
{
log.LogInformation($"Read blob content: {blobContent}");
return new OkObjectResult(new
{
content = blobContent,
length = blobContent?.Length ?? 0
});
}
}
Queue Input Binding
public static class QueueInputFunction
{
[FunctionName("ProcessQueueMessage")]
public static void Run(
[QueueTrigger("orders")] string orderMessage,
ILogger log)
{
log.LogInformation($"Processing order: {orderMessage}");
var order = JsonSerializer.Deserialize<Order>(orderMessage);
// Process order...
}
}
Table Input Binding
public static class TableInputFunction
{
[FunctionName("GetFromTable")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "customers/{partitionKey}/{rowKey}")] HttpRequest req,
[Table("customers", "{partitionKey}", "{rowKey}")] Customer customer,
ILogger log)
{
if (customer == null)
return new NotFoundResult();
return new OkObjectResult(customer);
}
}
Solution - Output Bindings
Blob Output Binding
public static class BlobOutputFunction
{
[FunctionName("WriteToBlob")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
[Blob("output/{filename}", FileAccess.Write)] Stream outputBlob,
ILogger log)
{
var content = await new StreamReader(req.Body).ReadToEndAsync();
// Simply write - binding handles the rest
var bytes = Encoding.UTF8.GetBytes(content);
await outputBlob.WriteAsync(bytes, 0, bytes.Length);
return new OkObjectResult(new { written = true });
}
}
Queue Output Binding
public static class QueueOutputFunction
{
[FunctionName("SendToQueue")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
[Queue("output-orders")] ICollector<string> outputQueue,
ILogger log)
{
var order = await req.ReadAsJsonAsync<Order>();
// Add to queue - binding handles serialization
outputQueue.Add(JsonSerializer.Serialize(order));
return new AcceptedResult();
}
}
Table Output Binding
public static class TableOutputFunction
{
[FunctionName("WriteToTable")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
[Table("audit")] ICollector<AuditRecord> tableBinding,
ILogger log)
{
var input = req.ReadFromJsonAsync<InputData>();
// Add to table
tableBinding.Add(new AuditRecord
{
PartitionKey = "audit",
RowKey = Guid.NewGuid().ToString(),
Timestamp = DateTime.UtcNow,
Action = input.action,
Details = input.details
});
return new OkObjectResult(new { saved = true });
}
}
public class AuditRecord
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTime Timestamp { get; set; }
public string Action { get; set; }
public string Details { get; set; }
}
Combined Input/Output Bindings
Process and Store Pattern
public static class ProcessAndStoreFunction
{
[FunctionName("ProcessAndStore")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
[Blob("input/{filename}")] Stream inputBlob,
[Blob("output/processed-{filename}")] Stream outputBlob,
ILogger log)
{
// Read from input blob
using var reader = new StreamReader(inputBlob);
var content = await reader.ReadToEndAsync();
// Process data
var processed = ProcessData(content);
// Write to output blob
var bytes = Encoding.UTF8.GetBytes(processed);
await outputBlob.WriteAsync(bytes, 0, bytes.Length);
return new OkObjectResult(new { processed = true });
}
private string ProcessData(string data)
{
// Transform data
return data.ToUpper();
}
}
Real-Time Scenarios
Scenario 1: Image Processing Pipeline
public static class ImageProcessingFunction
{
[FunctionName("ProcessImage")]
public static async Task Run(
[BlobTrigger("uploads/{name}")] Stream inputImage,
[Blob("uploads/{name}")] string imageName,
[Blob("thumbnails")] BlobContainerClient thumbContainer,
[Queue("thumbnail-jobs")] ICollector<ThumbnailJob> queue,
ILogger log)
{
// Load and resize image
using var input = Image.Load(inputImage);
input.Mutate(x => x.Resize(new ResizeOptions
{
Size = new Size(200, 200),
Mode = ResizeMode.Crop
}));
// Save thumbnail via binding
var thumbBlob = thumbContainer.GetBlobClient($"thumb_{imageName}");
using var ms = new MemoryStream();
input.SaveAsJpeg(ms);
ms.Position = 0;
await thumbBlob.UploadAsync(ms);
// Also queue notification
queue.Add(new ThumbnailJob
{
OriginalName = imageName,
ThumbUrl = thumbBlob.Uri.ToString()
});
log.LogInformation("Processed image: {Name}", imageName);
}
}
Scenario 2: Data Import with Validation
public static class DataImportFunction
{
[FunctionName("ImportData")]
public static async Task<IActionResult> Run(
[BlobTrigger("import/{filename}.csv")] string csvContent,
[Table("import-records")] ICollector<ImportRecord> table,
[Queue("import-errors")] ICollector<ImportError> errorQueue,
ILogger log)
{
var lines = csvContent.Split('\n');
var imported = 0;
var errors = 0;
for (int i = 1; i < lines.Length; i++)
{
try
{
var record = ParseLine(lines[i]);
if (Validate(record))
{
table.Add(new ImportRecord
{
PartitionKey = DateTime.UtcNow.ToString("yyyy-MM-dd"),
RowKey = Guid.NewGuid().ToString(),
Data = JsonSerializer.Serialize(record),
ImportedAt = DateTime.UtcNow
});
imported++;
}
else
{
errors++;
}
}
catch (Exception ex)
{
errorQueue.Add(new ImportError
{
LineNumber = i,
Error = ex.Message,
Content = lines[i]
});
errors++;
}
}
return new OkObjectResult(new { imported, errors });
}
}
Scenario 3: Webhook Processing
public static class WebhookFunction
{
[FunctionName("HandleWebhook")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[Table("webhooks")] ICollector<WebhookEvent> table,
[ServiceBus("webhook-events")] ICollector<string> serviceBus,
ILogger log)
{
var payload = await req.ReadAsStringAsync();
var eventData = JsonSerializer.Deserialize<WebhookPayload>(payload);
// Store in table
table.Add(new WebhookEvent
{
PartitionKey = eventData.EventType,
RowKey = eventData.EventId,
Timestamp = DateTime.UtcNow,
Source = eventData.Source,
Data = payload
});
// Publish to Service Bus for async processing
serviceBus.Add(JsonSerializer.Serialize(eventData));
return new OkResult();
}
}
Binding Expressions
Using Route Parameters
[FunctionName("GetFile")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "files/{container}/{filename}")] HttpRequest req,
[Blob("{container}/{filename}")] string content,
ILogger log)
{
return new OkObjectResult(content);
}
Using Query Parameters
[FunctionName("GetByQuery")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[Blob("files/{Query.filename}")] string content,
ILogger log)
{
return new OkObjectResult(content);
}
System Variables
[FunctionName("SystemInfo")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[Blob("logs/{Date}.txt", FileAccess.Write)] Stream logFile,
ILogger log)
{
var info = $"Processed at {DateTime.UtcNow} by function {context.FunctionName}";
return new OkObjectResult(info);
}
Best Practices
| Practice | Why |
|---|---|
| Use bindings for Azure services | Reduces boilerplate code |
| Use proper types (Stream vs string) | Better performance |
| Handle missing data gracefully | Return 404 for missing blobs |
| Use connection strings in config | Environment-specific settings |
Summary
- Input bindings read data declaratively
- Output bindings write data without SDK code
- Bindings simplify connection management
- Use expressions for dynamic values
- Combined bindings enable complete workflows