MultipartDataSerializer 1.0.1
dotnet add package MultipartDataSerializer --version 1.0.1
NuGet\Install-Package MultipartDataSerializer -Version 1.0.1
<PackageReference Include="MultipartDataSerializer" Version="1.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="MultipartDataSerializer" Version="1.0.1" />
<PackageReference Include="MultipartDataSerializer"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add MultipartDataSerializer --version 1.0.1
#r "nuget: MultipartDataSerializer, 1.0.1"
#:package MultipartDataSerializer@1.0.1
#addin nuget:?package=MultipartDataSerializer&version=1.0.1
#tool nuget:?package=MultipartDataSerializer&version=1.0.1
MultipartDataSerializer
A C# Source Generator that automatically creates extension methods to serialize objects to multipart/form-data format. Perfect for Blazor applications and ASP.NET Core APIs that need to handle file uploads with complex data.
✨ Features
- Zero boilerplate code - Automatically generates serialization methods at compile time
- Type-safe - Strongly typed with full IntelliSense support
- Nested object support - Handles complex objects with nested properties
- File uploads - Supports single
StreamorList<Stream>for multiple files - Blazor compatible - Works seamlessly with
IBrowserFileandInputFilecomponents - No runtime overhead - Everything happens at compile time
- Auto-updates - Regenerates when your models change
📦 Installation
Install via NuGet Package Manager:
dotnet add package MultipartDataSerializer
Or via Package Manager Console:
Install-Package MultipartDataSerializer
For Source Generator Projects
If you're referencing the source generator project directly:
<ItemGroup>
<ProjectReference Include="..\MultipartDataSerializer\MultipartDataSerializer.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
🚀 Quick Start
1. Define Your Model
Create a record or class with nested objects and optional Stream properties:
namespace YourApp.Models;
public record ProductRequest(ProductData Data)
{
public Stream? Photo { get; set; } // Single file (optional)
public List<Stream>? Photos { get; set; } // Multiple files (optional)
public record ProductData(Guid Id, string Name, decimal Price);
}
2. Use the Generated Extension Method
The ToMultipartFormData() method is automatically generated:
@page "/products/create"
@inject HttpClient HttpClient
<InputFile OnChange="HandleFileSelection" multiple />
<button @onclick="CreateProduct">Create Product</button>
@code {
private List<Stream>? selectedPhotos;
private async Task HandleFileSelection(InputFileChangeEventArgs e)
{
selectedPhotos = new List<Stream>();
foreach (var file in e.GetMultipleFiles())
{
selectedPhotos.Add(file.OpenReadStream(maxAllowedSize: 10_000_000));
}
}
private async Task CreateProduct()
{
var request = new ProductRequest(
new ProductRequest.ProductData(Guid.CreateVersion7(), "My Product", 99.99m)
)
{
Photos = selectedPhotos
};
// ToMultipartFormData() is automatically available!
using var content = request.ToMultipartFormData();
await HttpClient.PostAsync("/api/products", content);
}
}
3. Handle on the Server
using static YourApp.Models.ProductRequest;
app.MapPost("/api/products",
([FromForm] ProductData data,
IFormFile? Photo,
IFormFileCollection? Photos) =>
{
// Use data.Id, data.Name, data.Price
// Handle Photo and Photos uploads
return Results.Ok(new {
Message = $"Created {data.Name}",
PhotoCount = Photos?.Count ?? 0
});
})
.DisableAntiforgery();
📖 Documentation
Supported Property Types
The generator handles:
- ✅ Primitive types (
int,string,Guid,DateTime,decimal, etc.) - ✅ Nullable types (
int?,string?, etc.) - ✅
DateOnlywith automaticyyyy-MM-ddformatting - ✅ Decimal types with culture-invariant formatting
- ✅
StreamandStream?for single file uploads - ✅
List<Stream>for multiple file uploads - ✅ Nested objects (automatically flattened)
- ✅ Collections and lists
How It Works
- Compile-time analysis: The generator scans your code for types with
Streamproperties - Code generation: Creates a static extension method
ToMultipartFormData()for each type - Serialization strategy:
- Nested object properties → Individual form fields (e.g.,
Data.Id→"Id") Streamproperties → File attachments with unique names- Collections → Indexed form fields (e.g.,
Items[0].Name)
- Nested object properties → Individual form fields (e.g.,
Example: Complete Scenario
Model Definition
public record CreateUserRequest(UserInfo Info)
{
public Stream? ProfilePicture { get; set; }
public List<Stream>? Documents { get; set; }
public record UserInfo(string FirstName, string LastName, string Email, DateOnly BirthDate);
}
Client-Side (Blazor)
@page "/users/create"
@inject HttpClient Http
<EditForm Model="@model" OnValidSubmit="HandleSubmit">
<InputText @bind-Value="model.FirstName" />
<InputText @bind-Value="model.LastName" />
<InputText @bind-Value="model.Email" />
<InputDate @bind-Value="model.BirthDate" />
<InputFile OnChange="OnProfilePictureSelected" />
<InputFile OnChange="OnDocumentsSelected" multiple />
<button type="submit">Create User</button>
</EditForm>
@code {
private UserFormModel model = new();
private Stream? profilePicture;
private List<Stream>? documents;
private async Task OnProfilePictureSelected(InputFileChangeEventArgs e)
{
var file = e.File;
profilePicture = file.OpenReadStream();
}
private async Task OnDocumentsSelected(InputFileChangeEventArgs e)
{
documents = new List<Stream>();
foreach (var file in e.GetMultipleFiles(5))
{
documents.Add(file.OpenReadStream());
}
}
private async Task HandleSubmit()
{
var request = new CreateUserRequest(
new CreateUserRequest.UserInfo(
model.FirstName,
model.LastName,
model.Email,
model.BirthDate
)
)
{
ProfilePicture = profilePicture,
Documents = documents
};
using var content = request.ToMultipartFormData();
var response = await Http.PostAsync("/api/users", content);
if (response.IsSuccessStatusCode)
{
// Handle success
}
}
}
Server-Side (Minimal API)
using static MyApp.Models.CreateUserRequest;
app.MapPost("/api/users", async (
[FromForm] string FirstName,
[FromForm] string LastName,
[FromForm] string Email,
[FromForm] DateOnly BirthDate,
IFormFile? ProfilePicture,
IFormFileCollection? Documents) =>
{
var user = new UserInfo(FirstName, LastName, Email, BirthDate);
// Save profile picture
if (ProfilePicture != null)
{
var picturePath = Path.Combine("uploads", $"{Guid.NewGuid()}{Path.GetExtension(ProfilePicture.FileName)}");
using var stream = File.Create(picturePath);
await ProfilePicture.CopyToAsync(stream);
}
// Save documents
if (Documents != null)
{
foreach (var doc in Documents)
{
var docPath = Path.Combine("uploads", $"{Guid.NewGuid()}{Path.GetExtension(doc.FileName)}");
using var stream = File.Create(docPath);
await doc.CopyToAsync(stream);
}
}
return Results.Ok(new { Message = $"User {FirstName} {LastName} created successfully" });
})
.DisableAntiforgery();
🎯 Use Cases
- Blazor file uploads with complex form data
- API endpoints that need both structured data and files
- Multi-step forms with file attachments
- Profile management with avatar uploads
- Document management systems
- E-commerce product creation with images
⚙️ Requirements
- .NET Standard 2.0+ (for the generator)
- .NET 5.0+ (for projects using the generator)
- C# 9.0+ (for record types support)
- Microsoft.CodeAnalysis.CSharp 4.14.0+
🔧 Building & Publishing
To create a NuGet package:
cd MultipartDataSerializer
dotnet pack -c Release
The package will be created in bin/Release/MultipartDataSerializer.{version}.nupkg
To publish to NuGet:
dotnet nuget push bin/Release/MultipartDataSerializer.{version}.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json
📝 Generated Code Example
For the ProductRequest model above, the generator creates:
namespace YourApp.Models
{
public static class ProductRequestMultipartFormExtensions
{
public static MultipartFormDataContent ToMultipartFormData(
this ProductRequest obj,
CancellationToken cancellationToken = default)
{
var content = new MultipartFormDataContent();
// Serialize nested object properties
content.Add(new StringContent(obj.Data.Id.ToString()), "Id");
content.Add(new StringContent(obj.Data.Name), "Name");
content.Add(new StringContent(obj.Data.Price.ToString().Replace(",", ".")), "Price");
// Serialize single file
if (obj.Photo != null)
{
var fileContent = new StreamContent(obj.Photo);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
content.Add(fileContent, "Photo", $"{Guid.CreateVersion7()}.png");
}
// Serialize multiple files
for (int i = 0; i < obj.Photos?.Count; i++)
{
var fileContent = new StreamContent(obj.Photos[i]);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
content.Add(fileContent, $"Photos[{i}]", $"{Guid.CreateVersion7()}.png");
}
return content;
}
}
}
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🌟 Support
If you find this library useful, please consider:
- ⭐ Starring the repository
- 🐛 Reporting issues
- 💡 Suggesting new features
- 📖 Improving documentation
🔗 Links
Made with ❤️ for the .NET community
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.