DEV Community

Cover image for How to Package .NET Software: Choosing Between DLLs, SDKs, and NuGet Packages
Outdated Dev
Outdated Dev

Posted on

How to Package .NET Software: Choosing Between DLLs, SDKs, and NuGet Packages

Hello there!πŸ‘‹πŸ§”β€β™‚οΈ

As a software engineer, one of the most important decisions you'll make is how to package and deliver your code. Whether you're building a reusable library, sharing components across teams, or distributing a complete solution, the way you package your software directly impacts maintainability, scalability, and developer experience.

Get it right, and your code becomes easy to share, version, and maintain. Get it wrong, and you'll spend countless hours wrestling with dependency conflicts, version mismatches, and deployment headaches.

In the .NET ecosystem, you have three main approaches to package and distribute your software: DLLs, SDKs, and NuGet packages. Each has its place, and understanding when to use which approach is crucial for building robust, maintainable solutions.

Overview

When distributing .NET libraries and components, you have three main options:

  1. DLLs (Dynamic Link Libraries) - Direct binary distribution (the old-school way)
  2. SDKs (Software Development Kits) - Comprehensive development packages
  3. NuGet Packages - Modern package management solution (the industry standard)

Each approach has its place, but understanding their differences and trade-offs is essential for making informed decisions. Let's dive in!

1. DLL Distribution

DLLs are compiled binary files that can be referenced directly by other applications. In .NET, DLLs are assemblies containing IL code, metadata, and resources.

How it works:

  • Copy DLL files to your project
  • Reference via file path in the project file
  • Manually include all dependencies

Example:

<ItemGroup>
  <Reference Include="MyLibrary">
    <HintPath>..\libs\MyLibrary.dll</HintPath>
  </Reference>
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Pros: Simple for small projects, no external tooling

Cons: No version management, manual dependencies, no discoverability, deployment complexity

2. SDK Distribution

SDKs are comprehensive packages including libraries, tools, documentation, and samples. Modern SDKs typically use NuGet internally.

How it works:

  • Package multiple DLLs, tools, and documentation
  • Use NuGet packages with SDK-style projects
  • Provide analyzers, code generators, and build tools

Example:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="MyCompany.SDK" Version="1.0.0" />
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

Pros: Comprehensive solution, integrated tooling, analyzers and code generators

Cons: Heavier distribution, complex setup, maintenance overhead

3. NuGet Package Distribution

NuGet is the package manager for .NET, providing a standardized way to create, share, and consume libraries. It's integrated into Visual Studio, .NET CLI, and modern .NET workflows.

How to create NuGet packages:

dotnet new classlib -n MyLibrary
dotnet pack
dotnet nuget push MyLibrary.1.0.0.nupkg --source https://api.nuget.org/v3/index.json
Enter fullscreen mode Exit fullscreen mode

Package metadata (.csproj):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackageId>MyCompany.MyLibrary</PackageId>
    <Version>1.2.5</Version>
    <Authors>Your Name</Authors>
    <Description>Description of your library</Description>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
  </PropertyGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

Pros: Automatic dependency resolution, version management, multi-targeting, integrated tooling, private feeds

Cons: Requires understanding NuGet ecosystem, private feed setup can be complex

When to Use What?

Use DLLs when:

  • βœ… Working with legacy systems that can't use NuGet
  • βœ… Distributing to environments without internet access
  • βœ… Small internal tools with no dependencies
  • βœ… You need absolute control over file placement

Use SDKs when:

  • βœ… Building comprehensive developer tools
  • βœ… Need analyzers, code generators, or build tools
  • βœ… Providing a complete development environment
  • βœ… Complex scenarios requiring multiple components

Use NuGet when:

  • βœ… Almost always! (It's the modern standard)
  • βœ… Building libraries for distribution
  • βœ… Need automatic dependency management
  • βœ… Want version control and discoverability
  • βœ… Working with modern .NET projects

The Golden Rule: Start with NuGet. Only use DLLs or SDKs if you have a specific reason that NuGet can't handle.

Why NuGet is the Better Approach

NuGet solves the major pain points of DLL distribution:

1. Automatic Dependency Management

  • Handles transitive dependencies automatically
  • Eliminates "DLL hell" (version conflicts)
  • Example: Add Newtonsoft.Json and NuGet handles all its dependencies

2. Version Resolution

  • Semantic versioning with flexible ranges: [1.0.0, 2.0.0), 1.0.*, 1.0.0-*
  • Prevents breaking changes
  • Allows flexible version management

3. Multi-Targeting

  • Single package targets multiple frameworks:
<TargetFrameworks>net8.0;net6.0;netstandard2.1</TargetFrameworks>
Enter fullscreen mode Exit fullscreen mode

4. Rich Features

  • Rich metadata (description, license, repository links)
  • Integrated tooling (Visual Studio, VS Code, .NET CLI)
  • Private feeds for internal distribution
  • Symbol packages and Source Link for debugging
  • Supports libraries, tools, templates, and analyzers

Best Practices for NuGet Packages

Semantic Versioning: Follow SemVer (MAJOR.MINOR.PATCH, e.g., 1.2.3)

Naming: Use reverse domain notation (CompanyName.ProductName.FeatureName)

Dependencies: Only include necessary dependencies, use version ranges appropriately

Multi-targeting: Target lowest compatible frameworks (netstandard2.0 for max compatibility)

Metadata: Include comprehensive metadata:

<PropertyGroup>
  <PackageId>MyCompany.MyLibrary</PackageId>
  <Version>1.0.0</Version>
  <Description>Clear description</Description>
  <PackageTags>tag1;tag2</PackageTags>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
  <RepositoryUrl>https://github.com/yourcompany/mylibrary</RepositoryUrl>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Breaking Changes: Increment major version, document in release notes

Migrating from DLLs to NuGet

Step 1: Create class library and add package metadata to .csproj:

<PropertyGroup>
  <PackageId>MyCompany.MyLibrary</PackageId>
  <Version>1.0.0</Version>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Step 2: Pack and publish:

dotnet pack
dotnet nuget push MyCompany.MyLibrary.1.0.0.nupkg --source https://api.nuget.org/v3/index.json
Enter fullscreen mode Exit fullscreen mode

Step 3: Update consuming projects:

<!-- Remove DLL reference, add NuGet package -->
<ItemGroup>
  <PackageReference Include="MyCompany.MyLibrary" Version="1.0.0" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Benefits: Automatic dependency resolution, easy updates, better discoverability

Common NuGet Issues and Solutions

Package Restore Fails:

dotnet nuget locals all --clear
dotnet restore --verbosity detailed
Enter fullscreen mode Exit fullscreen mode

Version Conflicts: Use version ranges or Central Package Management:

<PackageReference Include="Newtonsoft.Json" Version="[13.0.0,14.0.0)" />
Enter fullscreen mode Exit fullscreen mode

Private Feed Authentication: Add source with credentials or use NuGet.config:

dotnet nuget add source <feed-url> --name "MyFeed" --username "user" --password "token"
Enter fullscreen mode Exit fullscreen mode

Package Too Large: Split packages, exclude unnecessary files (**/*.pdb Pack="false")

Comparison Summary

Feature DLLs SDKs NuGet
Dependency Resolution Manual Partial Automatic
Version Management None Limited Full
Discoverability Low Medium High
Multi-targeting Manual Limited Built-in
Tooling Integration None Good Excellent
Metadata Support None Limited Rich
Update Management Manual Manual Automatic
Private Distribution File share Complex Native support

Real-World Scenarios

Legacy Integration: Wrap legacy DLLs in a NuGet package:

<None Include="LegacyLibrary.dll" Pack="true" PackagePath="lib\net8.0\" />
Enter fullscreen mode Exit fullscreen mode

Internal Library: Use NuGet with private feed (Azure DevOps, GitHub Packages)

Open Source: Publish to NuGet.org with API key

Conclusion

While DLLs and SDKs have their place, NuGet is the modern standard because it:

  • Solves dependency management automatically (no more "DLL hell"!)
  • Provides robust version resolution
  • Integrates seamlessly with development tools
  • Enables discoverability and sharing

The Bottom Line: If you're still using DLLs, consider migrating to NuGetβ€”it'll save you time and headaches. Start with internal libraries, then expand to public packages.

You've got this! πŸ’ͺ Happy packaging!

Additional Resources

Top comments (0)