Skip to content

Commit

Permalink
Add Open-source Library Guidelines section to .NET Guide (dotnet#7833)
Browse files Browse the repository at this point in the history
* Add library guidelines section

* Add images and fix links

* Include cross-platform feedback

* Change guidelines to guidance

* Minor fix

* PR feedback

* Update breaking-changes.md with tool suggestions

* Update cross-platform-targeting.md with tool suggestions

* Update dependencies.md with tool suggestions

* Update nuget-publishing.md with tool suggestions

* Update nuget.md with tool suggestions

* Update sourcelink.md with tool suggestions

* Update strong-naming.md with tool suggestions

* Update versioning.md with tool suggestions

* PR feedback

* Minor improvements

* Update sourcelink

* Public shared source

* Add MVC compat version example to breaking changes

* Update behavior breaking changes

* Get started

* Fix video link

* Aspects of high-quality libraries

* Quote names in NuGet metadata table

* Derp

* Derp

* Intro aspects

* Add next/previous buttons

* Checked list

* Derp

* De-embiggen

* Merge more information links

* Fix broken links

* Update directory name

* PR feedback

* PR feedback

* Add breadcrumb

* Update media directory

* Derp

* PR feedback

* Change project to library where appropriate

* Derp

* quick edit

* removed comma

* quick edit

* quick edit

* quick edit

* quick edit

* quick edit

* quick edit

* quick edit

* PR feedback

* Typo
  • Loading branch information
JamesNK authored and mairaw committed Oct 8, 2018
1 parent 4122888 commit f232f61
Show file tree
Hide file tree
Showing 24 changed files with 711 additions and 0 deletions.
98 changes: 98 additions & 0 deletions docs/standard/library-guidance/breaking-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Breaking changes and .NET libraries
description: Best practice recommendations for navigating breaking changes when creating .NET libraries.
author: jamesnk
ms.author: mairaw
ms.date: 10/02/2018
---
# Breaking changes

It's important for a .NET library to find a balance between stability for existing users and innovation for the future. Library authors lean towards refactoring and rethinking code until it's perfect, but breaking your existing users has a negative impact, especially for low-level libraries.

## Project types and breaking changes

How a library is used by the .NET community changes the effect of breaking changes on end-user developers.

* **Low and middle-level libraries** like a serializer, HTML parser, database object-relational mapper, or web framework are the most affected by breaking changes.

Building block packages are used by end-user developers to build applications, and by other libraries as NuGet dependencies. For example, you're building an application and are using an open-source client to call a web service. A breaking update to dependency the client uses isn't something you can fix. It's the open-source client that needs to be changed and you have no control over it. You have to find compatible versions of the libraries or submit a fix to the client library and wait for a new version. The worst-case situation is if you want to use two libraries that depend on mutually incompatible versions of a third library.

* **High-level libraries** like a suite of UI controls are less sensitive to breaking changes.

High-level libraries are directly referenced in an end-user application. If breaking changes occur, the developer can choose to update to the latest version, or can modify their application to work with the breaking change.

**✔️ DO** think about how your library will be used. What effect will breaking changes have on applications and libraries that use it?

**✔️ DO** minimize breaking changes when developing a low-level .NET library.

**✔️ CONSIDER** publishing a major rewrite of a library as a new NuGet package.

## Types of breaking changes

Breaking changes fall into different categories and aren't equally impactful.

### Source breaking change

A source breaking change doesn't affect program execution but will cause compilation errors the next time the application is recompiled. For example, a new overload can create an ambiguity in method calls that were unambiguous previously, or a renamed parameter will break callers that use named parameters.

```csharp
public class Task
{
// Adding a type called Task could conflict with System.Threading.Tasks.Task at compilation
}
```

Because a source breaking change is only harmful when developers recompile their applications, it's the least disruptive breaking change. Developers can fix their own broken source code easily.

### Behavior breaking change

Behavior changes are the most common type of breaking change: almost any change in behavior could break someone. Changes to your library, such as method signatures, exceptions thrown or input or output data formats, could all negatively impact your library consumers. Even a bug fix can qualify as a breaking change if users relied on the previously broken behavior.

Adding features and improving bad behaviors is a good thing, but without care it can make it very hard for existing users to upgrade. One approach to helping developers deal with behavior breaking changes is to hide them behind settings. Settings let developers update to the latest version of your library while at the same time choosing to opt in or opt out of breaking changes. This strategy lets developers stay up to date while letting their consuming code adapt over time.

For example, ASP.NET Core MVC has the concept of a [compatibility version](/aspnet/core/mvc/compatibility-version) that modifies the features enabled and disabled on `MvcOptions`.

**✔️ CONSIDER** leaving new features off by default, if they affect existing users, and let developers opt in to the feature with a setting.

### Binary breaking change

A binary breaking change happens when you change the public API of your library, so assemblies compiled against older versions of your library are no longer able to call the API. For example, changing a method's signature by adding a new parameter will cause assemblies compiled against the older version of the library to throw a <xref:System.MissingMethodException>.

A binary breaking change can also break an **entire assembly**. Renaming an assembly with `AssemblyName` will change the assembly's identity, as will adding, removing, or changing the assembly's strong naming key. A change of an assembly's identity will break all compiled code that uses it.

**❌ DO NOT** change an assembly name.

**❌ DO NOT** add, remove, or change the strong naming key.

**✔️ CONSIDER** using abstract base classes instead of interfaces.

> Adding anything to an interface will cause existing types that implement it to fail. An abstract base class allows you to add a default virtual implementation.
**✔️ CONSIDER** placing the <xref:System.ObsoleteAttribute> on types and members that you intend to remove. The attribute should have instructions for updating code to no longer use the obsolete API.

> Code that calls types and methods with the <xref:System.ObsoleteAttribute> will generate a build warning with the message supplied to the attribute. The warnings give people who use the obsolete API surface time to migrate so that when the obsolete API is removed, most are no longer using it.
```csharp
public class Document
{
[Obsolete("LoadDocument(string) is obsolete. Use LoadDocument(Uri) instead.")]
public static Document LoadDocument(string uri)
{
return LoadDocument(new Uri(uri));
}

public static Document LoadDocument(Uri uri)
{
// Load the document
}
}
```

## See also

* [Version and update considerations for C# developers](../../csharp/whats-new/version-update-considerations.md)
* [A definitive guide to API-breaking changes in .NET](https://stackoverflow.com/questions/1456785/a-definitive-guide-to-api-breaking-changes-in-net)
* [CoreFX Breaking Change Rules](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md)

>[!div class="step-by-step"]
[Previous](./versioning.md)
93 changes: 93 additions & 0 deletions docs/standard/library-guidance/cross-platform-targeting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Cross-platform targeting
description: Best practice recommendations for creating cross-platform .NET libraries.
author: jamesnk
ms.author: mairaw
ms.date: 10/02/2018
---
# Cross-platform targeting

Modern .NET supports multiple operating systems and devices. It's important for .NET open-source libraries to support as many developers as possible, whether they're building an ASP.NET website hosted in Azure, or a .NET game in Unity.

## .NET Standard

.NET Standard is the best way to add cross-platform support to a .NET library. [.NET Standard](../net-standard.md) is a specification of .NET APIs that are available on all .NET implementations. Targeting .NET Standard lets you produce libraries that are constrained to use APIs that are in a given version of .NET Standard, which means it's usable by all platforms that implement that version of the .NET Standard.

![.NET Standard](./media/cross-platform-targeting/platforms-netstandard.png ".NET Standard")

Targeting .NET Standard, and successfully compiling your project, doesn't guarantee the library will run successfully on all platforms:

1. Platform-specific APIs will fail on other platforms. For example, <xref:Microsoft.Win32.Registry?displayProperty=nameWithType> will succeed on Windows and throw <xref:System.PlatformNotSupportedException> when used on any other OS.
2. APIs can behave differently. For example, reflection APIs have different performance characteristics when an application uses ahead-of-time compilation on iOS or UWP.

> [!TIP]
> The .NET team [offers a Roslyn analyzer](../analyzers/api-analyzer.md) to help you discover possible issues.
**✔️ DO** start with including a `netstandard2.0` target.

> Most general-purpose libraries should not need APIs outside of .NET Standard 2.0. .NET Standard 2.0 is supported by all modern platforms and is the recommended way to support multiple platforms with one target.
**❌ AVOID** including a `netstandard1.x` target.

> .NET Standard 1.x is distributed as a granular set of NuGet packages, which creates a large package dependency graph and results in developers downloading a lot of packages when building. Modern .NET platforms, including .NET Framework 4.6.1, UWP and Xamarin, all support .NET Standard 2.0. You should only target .NET Standard 1.x if you specifically need to target an older platform.
**✔️ DO** include a `netstandard2.0` target if you require a `netstandard1.x` target.

> All platforms supporting .NET Standard 2.0 will use the `netstandard2.0` target and benefit from having a smaller package graph while older platforms will still work and fall back to using the `netstandard1.x` target.
**❌ DO NOT** include a .NET Standard target if the library relies on a platform-specific app model.

> For example, a UWP control toolkit library depends on an app model that is only available on UWP. App model specific APIs will not be available in .NET Standard.
## Multi-targeting

Sometimes you need to access framework-specific APIs from your libraries. The best way to call framework-specific APIs is using multi-targeting, which builds your project for many [.NET target frameworks](../frameworks.md) rather than for just one.

To shield your consumers from having to build for individual frameworks, you should strive to have a .NET Standard output plus one or more framework-specific outputs. With multi-targeting, all assemblies are packaged inside a single NuGet package. Consumers can then reference the same package and NuGet will pick the appropriate implementation. Your .NET Standard library serves as the fallback library that is used everywhere, except for the cases where your NuGet package offers a framework-specific implementation. Multi-targeting allows you to use conditional compilation in your code and call framework-specific APIs.

![NuGet package with multiple assemblies](./media/cross-platform-targeting/nuget-package-multiple-assemblies.png "NuGet package with multiple assemblies")

**✔️ CONSIDER** targeting .NET implementations in addition to .NET Standard.

> Targeting .NET implementations allows you to call platform-specific APIs that are outside of .NET Standard.
>
> Do not drop support for .NET Standard when you do this. Instead, throw from the implementation and offer capability APIs. This way, your library can be used anywhere and supports runtime light-up of features.
**❌ AVOID** using multi-targeting with .NET Standard if your source code is the same for all targets.

> The .NET Standard assembly will automatically be used by NuGet. Targeting individual .NET implementations increases the `*.nupkg` size for no benefit.
**✔️ CONSIDER** adding a target for `net461` when you're offering a `netstandard2.0` target.

> Using .NET Standard 2.0 from .NET Framework has some issues that were addressed in .NET Framework 4.7.2. You can improve the experience for developers that are still on .NET Framework 4.6.1 - 4.7.1 by offering them a binary that is built for .NET Framework 4.6.1.
**✔️ DO** distribute your library using a NuGet package.

> NuGet will select the best target for the developer and shield them having to pick the appropriate implementation.
**✔️ DO** use a project file's `TargetFrameworks` property when multi-targeting.

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- This project will output netstandard2.0 and net471 assemblies -->
<TargetFrameworks>netstandard2.0;net471</TargetFrameworks>
</PropertyGroup>
</Project>
```

**✔️ CONSIDER** using [MSBuild.Sdk.Extras](https://github.com/onovotny/MSBuildSdkExtras) when multi-targeting for UWP and Xamarin as it greatly simplifies your project file.

## Older targets

.NET supports targeting versions of the .NET Framework that are long out of support as well as platforms that are no longer commonly used. While there's value in making your library work on as many targets as possible, having to work around missing APIs can add significant overhead. We believe certain frameworks are no longer worth targeting, considering their reach and limitations.

**❌ DO NOT** include a Portable Class Library (PCL) target. For example, `portable-net45+win8+wpa81+wp8`.

> .NET Standard is the modern way to support cross-platform .NET libraries and replaces PCLs.
**❌ DO NOT** include targets for .NET platforms that are no longer supported. For example, `SL4`, `WP`.

>[!div class="step-by-step"]
[Previous](./get-started.md)
[Next](./strong-naming.md)
95 changes: 95 additions & 0 deletions docs/standard/library-guidance/dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
title: Dependencies and .NET libraries
description: Best practice recommendations for managing NuGet dependencies in .NET libraries.
author: jamesnk
ms.author: mairaw
ms.date: 10/02/2018
---
# Dependencies

The primary way of adding dependencies to a .NET library is referencing NuGet packages. NuGet package references allow you to quickly reuse and leverage already written functionality, but they're a common source of friction for .NET developers. Correctly managing dependencies is important to prevent changes in other .NET libraries from breaking your .NET library, and vice versa!

## Diamond dependencies

It's a common situation for a .NET project to have multiple versions of a package in its dependency tree. For example, an app depends on two NuGet packages, each of which depends on different versions of the same package. A diamond dependency now exists in the app's dependency graph.

![Diamond dependency](./media/dependencies/diamond-dependency.png "Diamond dependency")

At build time, NuGet analyzes all the packages that a project depends on, including the dependencies of dependencies. When multiple versions of a package are detected, rules are evaluated to pick one. Unifying packages is necessary because running side-by-side versions of an assembly in the same application is problematic in .NET.

Most diamond dependencies are easily resolved; however, they can create issues in certain circumstances:

1. **Conflicting NuGet package references** prevent a version from being resolved during package restore.
2. **Breaking changes between the versions** cause bugs and exceptions at runtime.
3. **The package assembly is strong named**, the assembly version changed, and the app is running on the .NET Framework. Assembly binding redirects are required.

It's not possible to know what packages will be used alongside your own. A good way to reduce the likelihood of a diamond dependency breaking your library is to minimize the number of packages you depend on.

**✔️ DO** review your .NET library for unnecessary dependencies.

## NuGet dependency version ranges

A package reference specifies the range of valid packages it allows. Typically, the package reference version in the project file is the minimum version and there's no maximum.

```xml
<!-- Accepts any version 1.0 and above. -->
<PackageReference Include="ExamplePackage" Version="1.0" />
```

The rules that NuGet uses when resolving dependencies are [complex](/nuget/consume-packages/dependency-resolution), but NuGet always looks for the lowest applicable version. NuGet prefers the lowest application version over using the highest available because the lowest will have the least compatibility issues.

Because of NuGet's lowest application version rule, it isn't necessary to place an upper version or exact range on package references to avoid getting the latest version. NuGet already tries to find the lowest, most compatible version for you.

```xml
<!-- Accepts 1.0 up to 1.x, but not 2.0 and higher. -->
<PackageReference Include="ExamplePackage" Version="[1.0,2.0)" />

<!-- Accepts exactly 1.0. -->
<PackageReference Include="ExamplePackage" Version="[1.0]" />
```

Upper version limits will cause NuGet to fail if there's a conflict. For example, one library accepts exactly 1.0 while another library requires 2.0 or above. While breaking changes may have been introduced in version 2.0, a strict or upper limit version dependency guarantees an error.

![Diamond dependency conflict](./media/dependencies/diamond-dependency-conflict.png "Diamond dependency conflict")

**❌ DO NOT** have NuGet package references with no minimum version.

**❌ AVOID** NuGet package references that demand an exact version.

**❌ AVOID** NuGet package references with a version upper limit.

## NuGet shared source packages

One way to reduce external NuGet package dependencies is to reference shared source packages. A shared source package contains [source code files](/nuget/reference/nuspec#including-content-files) that are included in a project when referenced. Because you're just including source code files that are compiled with the rest of your project, there's no external dependency and chance of conflict.

Shared source packages are great for including small pieces of functionality. For example, a shared source package of helper methods for making HTTP calls.

![Shared source package](./media/dependencies/shared-source-package.png "Shared source package")

```xml
<PackageReference Include="Microsoft.Extensions.Buffers.Testing.Sources" PrivateAssets="All" Version="1.0" />
```

![Shared source project](./media/dependencies/shared-source-project.png "Shared source project")

Shared source packages have some limitations. They can only be referenced by `PackageReference`, so older `packages.config` projects are excluded. Also shared source packages are only usable by projects with the same language type. Because of these limitations shared source packages are best used to share functionality within an open-source project.

**✔️ CONSIDER** referencing shared source packages for small, internal pieces of functionality.

**✔️ CONSIDER** making your package a shared source package if it provides small, internal pieces of functionality.

**✔️ DO** reference shared source packages with `PrivateAssets="All"`.

> This setting tells NuGet the package is only to be used at development time and shouldn't be exposed as a public dependency.
**❌ DO NOT** have shared source package types in your public API.

> Shared source types are compiled into the referencing assembly and can't be exchanged across assembly boundaries. For example, a shared-source `IRepository` type in one project is a separate type from the same shared-source `IRepository` in another project. Types in shared source packages should have an `internal` visibility.
**❌ DO NOT** publish shared source packages to nuget.org.

> Shared source packages contain source code and can only be used by projects with the same language type. For example, a C# shared source package cannot be used by an F# application.
>[!div class="step-by-step"]
[Previous](./nuget.md)
[Next](./sourcelink.md)
Loading

0 comments on commit f232f61

Please sign in to comment.