MultipartDataSerializer 1.0.1

dotnet add package MultipartDataSerializer --version 1.0.1
                    
NuGet\Install-Package MultipartDataSerializer -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MultipartDataSerializer" Version="1.0.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MultipartDataSerializer" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="MultipartDataSerializer">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MultipartDataSerializer --version 1.0.1
                    
#r "nuget: MultipartDataSerializer, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MultipartDataSerializer@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MultipartDataSerializer&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=MultipartDataSerializer&version=1.0.1
                    
Install as a Cake Tool

MultipartDataSerializer

NuGet License: MIT

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 Stream or List<Stream> for multiple files
  • Blazor compatible - Works seamlessly with IBrowserFile and InputFile components
  • 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.)
  • DateOnly with automatic yyyy-MM-dd formatting
  • ✅ Decimal types with culture-invariant formatting
  • Stream and Stream? for single file uploads
  • List<Stream> for multiple file uploads
  • ✅ Nested objects (automatically flattened)
  • ✅ Collections and lists

How It Works

  1. Compile-time analysis: The generator scans your code for types with Stream properties
  2. Code generation: Creates a static extension method ToMultipartFormData() for each type
  3. Serialization strategy:
    • Nested object properties → Individual form fields (e.g., Data.Id"Id")
    • Stream properties → File attachments with unique names
    • Collections → Indexed form fields (e.g., Items[0].Name)

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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. 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

Made with ❤️ for the .NET community

There are no supported framework assets in this package.

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.

Version Downloads Last Updated
1.0.1 120 3/4/2026
1.0.0 298 11/11/2025