Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Contributor guide for coding agents

This file is for agents contributing to **this repository**. If you are *using* the
installed `CloudinaryDotNet` NuGet package in another project, read the bundled docs
instead — they ship inside the package and are version-matched to what you have installed:

```bash
ROOT=$(dotnet nuget locals global-packages --list | awk '{print $2}')
ls "$ROOT"/cloudinarydotnet/*/docs
```

The same pages are in [`docs/`](docs/README.md) in this repository.

## Commands

```bash
dotnet restore CloudinaryDotnet.sln
dotnet build CloudinaryDotnet.sln -c Release
dotnet test CloudinaryDotNet.Tests/CloudinaryDotNet.Tests.csproj -c Release -f net8.0
dotnet pack CloudinaryDotNet/CloudinaryDotNet.csproj -c Release -o ./artifacts
```

The main project multi-targets `netstandard1.3;netstandard2.0;net452`; the test projects
target `net452;net8.0`. On macOS and Linux only the `net8.0` test target runs — pass
`-f net8.0` explicitly, or the run fails looking for the .NET Framework host.

## Testing

- `CloudinaryDotNet.Tests/` — unit tests, mocked, no network. These must stay offline.
- `CloudinaryDotNet.IntegrationTests/` — requires a real product environment via
`CLOUDINARY_URL`. Do not run by default; do not add tests here that consume paid add-ons
without a skip guard.
- `examples/` — runnable docs examples. Not part of the solution and not covered by the test
suite; run them by hand against a throwaway cloud (`npx @cloudinary/cloud`).
- Nondeterministic AI output (captions, tags, moderation verdicts) must be asserted by
request shape, state transition, and response schema — never by exact output values.

## Project structure

- `CloudinaryDotNet/` — the library. `Cloudinary.cs` plus the `Cloudinary.*.cs` partials
split the API surface (`UploadApi`, `AdminApi`, `AdminApi.MetadataFields`, …).
- `CloudinaryDotNet/Actions/` — every parameter and result type. Public API consumers need
`using CloudinaryDotNet.Actions;` for these.
- `CloudinaryDotNet/Transforms/`, `Url.cs`, `UrlBuilder.cs` — URL and transformation
building; entirely local, no network.
- `CloudinaryDotNet/Search/` — the fluent Search API.
- `CloudinaryDotNet/Provisioning/` — account provisioning, a separate client.
- `docs/` — agent-facing task documentation, **shipped in the NuGet package**.
- `examples/` — runnable counterparts to the doc pages, deliberately *not* packaged.
- `samples/` — legacy sample applications (PhotoAlbum, LargeVideoUpload). Treat as legacy:
do not modernize them as part of unrelated work.
- `Cloudinary/`, `Core/`, `Shared/`, `Shared.Tests/`, `Cloudinary.Test*/` — legacy
scaffolding retained for the old build layout. Prefer the top-level projects.

## Code style

- StyleCop and FxCop analyzers run with `TreatWarningsAsErrors`. A style violation fails
the build; fix it rather than suppressing it.
- XML doc comments are required on public members (`GenerateDocumentationFile` is on).
- `LangVersion` is 9.0 for the library, and it targets `netstandard1.3`, so newer BCL APIs
and language features are unavailable there. Guard target-specific code with
`#if NETSTANDARD2_0` as the existing code does (see `ApiShared.Proxy.cs`).
- Public API additions need both sync and `…Async` overloads, following the existing
pattern.

## Error-handling contract

This SDK **returns** Cloudinary API errors rather than throwing: results derive from
`BaseResult`, which carries `StatusCode` and `Error`. Preserve this. Do not add throwing
behaviour to an existing API path, and do not introduce a custom exception type without
discussion — there are currently none, and callers rely on that.

## Versioning

The version lives in **two** places and both must match:

- `CloudinaryDotNet/CloudinaryDotNet.csproj` — `<Version>`
- `CloudinaryDotNet/CloudinaryVersion.cs` — `Full`

`set_version.ps1` updates both. It replaces the **first** `<Version>` element in the
csproj by regex, so never add another `<Version>` above it.

The bundled `docs/` carry **no version number** — the version-matched guarantee comes from
shipping inside the package. Do not add a version stamp to the docs.

## Git workflow

- Branch from `master`; keep changes focused; one topic per pull request.
- Build and run the unit tests before opening a PR.
- Do not rewrite published `CHANGELOG.md` entries; add new entries at the top. Docs-only
changes get no changelog entry.
- Never commit credentials, `.env` files, or `appsettings.json` with real values.

## Boundaries

**Always**
- Keep `docs/` and `examples/` consistent with the code they document.
- Verify a documented behaviour by running it against a real cloud before writing it down.
Reading the source and inferring behaviour has produced wrong documentation repeatedly.
- Keep API secrets out of examples, docs, tests, and fixtures.

**Ask first**
- Changing the packaged file list in `CloudinaryDotNet.csproj` (the `<None ... Pack="true">`
items), or anything else about packaging.
- Changing target frameworks, dependencies, or the analyzer/ruleset configuration.
- Renaming or removing any public type or member — this library is widely deployed.
- Changing release, CI (`appveyor.yml`), or signing configuration.

**Never**
- Commit credentials or real account identifiers.
- Perform live network calls from unit tests.
- Document a Cloudinary platform capability as an SDK method unless this package implements
it (see [docs/platform-capabilities.md](docs/platform-capabilities.md)).
- Add a linter, formatter, or reformat unrelated files.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
1 change: 1 addition & 0 deletions CloudinaryDotNet/CloudinaryDotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@
<ItemGroup>
<None Include=".\CloudinaryIcon.png" Pack="true" PackagePath="\" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
<None Include="..\docs\**\*.md" Pack="true" PackagePath="docs\" />
</ItemGroup>
</Project>
69 changes: 65 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[![Build status](https://ci.appveyor.com/api/projects/status/vdx8o03ethg5opt4?svg=true)](https://ci.appveyor.com/project/Cloudinary/cloudinarydotnet)
[![NuGet Badge](https://img.shields.io/nuget/v/CloudinaryDotNet)](https://www.nuget.org/packages/CloudinaryDotNet/)
![NuGet Downloads](https://img.shields.io/nuget/dt/CloudinaryDotNet)
[![License](https://img.shields.io/github/license/cloudinary/CloudinaryDotNet)](LICENSE)

Cloudinary .NET SDK
==================
Expand All @@ -20,6 +21,15 @@ For the complete documentation, see the [.NET SDK Guide](https://cloudinary.com/
- [Usage](#usage)
- [Setup](#Setup)
- [Transform and Optimize Assets](#Transform-and-Optimize-Assets)
- [Upload](#Upload)
- [Error handling](#Error-handling)
- [Code Samples](#Code-Samples)
- [Documentation for AI coding agents](#documentation-for-ai-coding-agents)
- [Contributions](#contributions)
- [Get Help](#get-help)
- [About Cloudinary](#about-cloudinary)
- [Additional Resources](#additional-resources)
- [Licence](#licence)


## Key Features
Expand All @@ -46,16 +56,24 @@ Install using Package Manager:
PM> Install-Package CloudinaryDotNet
```

Or using the .NET CLI:
```bash
dotnet add package CloudinaryDotNet
```

# Usage

### Setup
```csharp
using CloudinaryDotNet;
using CloudinaryDotNet.Actions;

var cloudinary = new Cloudinary();
var cloudinary = new Cloudinary(); // reads the CLOUDINARY_URL environment variable
cloudinary.Api.Secure = true; // generate https:// URLs
```

Note that `Api.Secure` defaults to `false`, so set it unless you specifically want `http://` URLs.

### Transform and Optimize Assets
- [See full documentation](https://cloudinary.com/documentation/dotnet_image_manipulation).

Expand All @@ -75,19 +93,62 @@ var uploadParams = new ImageUploadParams()
var uploadResult = cloudinary.Upload(uploadParams);
```

### Error handling

Cloudinary API errors are **returned, not thrown**. Check `Error` on every result:

```csharp
var uploadResult = await cloudinary.UploadAsync(uploadParams);

if (uploadResult.Error != null)
{
Console.Error.WriteLine($"Upload failed ({(int)uploadResult.StatusCode}): {uploadResult.Error.Message}");
return;
}

Console.WriteLine(uploadResult.SecureUrl);
```

A `try`/`catch` around an API call will not catch a Cloudinary error — only configuration
and argument problems throw. See [docs/troubleshoot-errors.md](docs/troubleshoot-errors.md).

### Code Samples

You can find our simple and ready-to-use samples projects, along with documentations in the [samples folder](https://github.com/cloudinary/CloudinaryDotNet/tree/master/samples).
You can find our simple and ready-to-use samples projects, along with documentations in the [samples folder](https://github.com/cloudinary/CloudinaryDotNet/tree/master/samples).

Please consult with the [README file](https://github.com/cloudinary/CloudinaryDotNet/blob/master/samples/README.md), for usage and explanations.

Task-focused runnable examples live in [`examples/`](examples/README.md).

### Security options
- [See full documentation](https://cloudinary.com/documentation/solution_overview#security).
- To report a vulnerability, see [SECURITY.md](SECURITY.md).

## Documentation for AI coding agents

This package ships task documentation **inside the NuGet package**, so it always matches the
version you have installed. If you are an AI coding agent — or you use one — point it there
rather than at training data, which is frequently out of date for this SDK.

Locate the installed copy:

```bash
ROOT=$(dotnet nuget locals global-packages --list | awk '{print $2}')
ls "$ROOT"/cloudinarydotnet/*/docs
```

The same pages are browsable here: [`docs/`](docs/README.md) — covering configuration,
upload, chunked video upload, signed browser uploads, image and video delivery, search and
asset management, moderation, structured metadata, ASP.NET Core integration, and
troubleshooting.

Agents contributing to this repository should read [AGENTS.md](AGENTS.md) instead.

## Contributions
- Ensure tests run locally
- Open a PR and ensure Travis tests pass
- Ensure tests run locally (`dotnet test CloudinaryDotNet.Tests/CloudinaryDotNet.Tests.csproj -c Release -f net8.0`)
- Open a PR and ensure the CI build passes

See [CONTRIBUTING.md](CONTRIBUTING.md) for details.


## Get Help
Expand Down
39 changes: 39 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Security Policy

## Supported versions

| Version | Supported |
|---------|-----------|
| 1.x | Yes |

## Reporting a vulnerability

Report vulnerabilities privately through [GitHub private vulnerability reporting](https://github.com/cloudinary/CloudinaryDotNet/security/advisories/new) for this repository.

If you cannot use GitHub reporting, contact Cloudinary support at [support.cloudinary.com](https://support.cloudinary.com/hc/en-us/requests/new) and mark the ticket as a security issue.

Use these private channels for anything security-sensitive; public GitHub issues are for regular bugs and feature requests.

## What to include in a report

- The affected package version and target framework (.NET Framework, .NET Standard, or .NET version).
- A minimal reproduction or proof of concept.
- The impact you believe the issue has (for example: credential exposure, signature bypass, request forgery).
- Any suggested remediation, if you have one.

## Response and disclosure process

- We acknowledge reports and keep you informed while the issue is investigated.
- Fixes are released as patched package versions; the changelog notes security-relevant changes without disclosing exploit details before users can upgrade.
- Please give us reasonable time to release a fix before public disclosure.

## Security guidance for SDK users

- Your API secret is a server-side credential. Keep it on your server; browsers, mobile binaries, and repositories should only ever hold delivery URLs or short-lived signatures.
- **Never reference this package from a client-side project** — Blazor WebAssembly, MAUI, Unity, or a desktop app. Anything the user can download, they can read. Call your own backend instead.
- Provide credentials through the `CLOUDINARY_URL` environment variable, or a secret store, rather than hardcoding them or committing them in `appsettings.json`.
- For uploads initiated from a browser or mobile app, generate the signature on your server. See [docs/sign-browser-upload.md](docs/sign-browser-upload.md).
- For unsigned uploads, use a deliberately restricted [unsigned upload preset](https://cloudinary.com/documentation/upload_presets) ([md](https://cloudinary.com/documentation/upload_presets.md)).
- Do not log a full Admin or Upload API request object; it can contain your credentials. Log `result.Error.Message` instead.
- Verify webhook callbacks with `Api.VerifyNotificationSignature` before acting on them.
- Cloudinary platform security documentation: https://cloudinary.com/documentation/solution_overview#security
4 changes: 4 additions & 0 deletions context7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"url": "https://context7.com/cloudinary/CloudinaryDotNet",
"public_key": "pk_dAgXWo5YsHXdnbg3TCE9R"
}
90 changes: 90 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!-- BEGIN:cloudinary-agent-rules
These docs match the installed package version. Training data about this SDK may be
outdated. The Markdown files in this directory are the source of truth for the version
you have installed. Locate them with:
ROOT=$(dotnet nuget locals global-packages --list | awk '{print $2}')
ls "$ROOT"/cloudinarydotnet/*/docs
END:cloudinary-agent-rules -->

# CloudinaryDotNet — bundled documentation

> **Version-matched:** these docs ship inside the NuGet package and always describe the
> version you have installed. Prefer them over anything remembered from training data
> or found for another version.

Task documentation for the `CloudinaryDotNet` server-side SDK. Each page is
self-contained: `using` directives, configuration, a complete runnable flow, expected
results, and common failures.

Runnable versions of most tasks are in the [`examples/`](https://github.com/cloudinary/CloudinaryDotNet/tree/master/examples)
directory of the repository. They are **not** shipped in the NuGet package — see
[Why examples are not in the package](#why-examples-are-not-in-the-package).

## Start here

- [What this SDK does and does not do](platform-capabilities.md) — the agent tooling to
set up first (Skills, MCP servers, CLI, documentation indexes), what this package
covers, and what lives elsewhere on the platform.
- [Get Cloudinary credentials](get-credentials.md) — no account needed: provision a cloud
and start building.
- [Install and call the SDK](install-and-call.md) — the `using` directives, the
`Cloudinary` entry point, and the sync/async pattern.

## Tasks

- [Configure Cloudinary](configure-cloudinary.md)
- [Upload an image](upload-image.md)
- [Upload a large video](upload-large-video.md)
- [Sign a browser upload](sign-browser-upload.md)
- [Transform and deliver an image](transform-and-deliver-image.md)
- [Transform and deliver a video](transform-and-deliver-video.md)
- [Search and manage assets](search-and-manage-assets.md)
- [Moderate an upload](moderate-upload.md)
- [Use structured metadata](use-structured-metadata.md)
- [Use with ASP.NET Core](use-with-aspnet-core.md)
- [Troubleshoot errors](troubleshoot-errors.md)

## The one thing to know before you write any code

**This SDK does not throw on Cloudinary API errors.** Every Upload and Admin API method
returns a result object carrying `StatusCode` and `Error`. A wrong `api_secret`, a
missing asset, or a rejected parameter all return *normally* — a `try`/`catch` around
the call catches nothing and your code proceeds with an empty result.

```csharp
var result = await cloudinary.UploadAsync(uploadParams);
if (result.Error != null)
{
Console.Error.WriteLine($"Upload failed ({(int)result.StatusCode}): {result.Error.Message}");
return;
}
```

Check `result.Error` after every call. See [Troubleshoot errors](troubleshoot-errors.md)
for the full error model, including the few cases that *do* throw.

## Security boundary

This is a **server-side** SDK. It holds your `ApiSecret`, which belongs on your server
only. Never ship it in a desktop, mobile, Blazor WebAssembly, or any other client-side
build — anything the user can download, they can read. Client code should receive
delivery URLs or short-lived signatures generated by your server
([how](sign-browser-upload.md)).

## Why examples are not in the package

The NuGet package ships `docs/` only. The `examples/` directory contains files with
top-level statements and a `Main` entry point; shipping loose `.cs` files inside a
package risks them being picked up by a consumer's compile glob and colliding with their
own entry point. Every page below therefore carries its complete runnable flow inline, so
you never need the examples directory to complete a task.

## Canonical docs

- [.NET SDK guide](https://cloudinary.com/documentation/dotnet_integration.md)
- [Full platform reference](https://cloudinary.com/documentation/cloudinary_references.md)

**Link convention:** documentation links in these docs end in `.md` and return raw
Markdown — the preferred format for agents and for anything that parses text. Remove the
`.md` suffix for the same page as browsable HTML. The repository README links the HTML
form first, since it is read by people.
Loading