You do not need to automate Nextcloud’s browser interface to retrieve a report, import documents, or build a file picker. Nextcloud exposes authenticated file operations through WebDAV: an HTTP API for browsing collections, reading metadata, and transferring files.
For a configured DavClient, a basic listing is small:
var listing = await client.PropFindAsync(
DavPath.Root, cancellationToken: cancellationToken);
listing.EnsureSucceeded();
foreach (var resource in listing.Resources)
{
Console.WriteLine(resource.Hrefs[0]);
}
The important work is around that call: choosing the right endpoint, using an app password, treating property failures correctly, and making local-file behavior explicit.
This walkthrough uses WebDAVClient, an async .NET library that handles DAV requests and XML responses. The examples target the published 3.2.0 package and work with its .NET 8 and .NET 10 targets. No unreleased API is required.
The Nextcloud configuration below follows its official API documentation. It is not a claim that every Nextcloud version, storage backend, or reverse proxy has been independently verified with the package.
1. Copy the WebDAV URL, not the browser URL
In Nextcloud’s Files application, find the WebDAV address in its settings. A typical authenticated file endpoint looks like this:
https://cloud.example.com/nextcloud/remote.php/dav/files/alice/
Three details matter:
- Keep the installation prefix. In this example,
/nextcloud/is part of the address. - Use the user ID supplied in the WebDAV URL. A display name or email address is not necessarily the same identifier.
- Keep the trailing slash: the endpoint represents a collection.
For this tutorial, use the URL of the collection containing the file you want to download. For example, a file inside Documents would use:
https://cloud.example.com/nextcloud/remote.php/dav/files/alice/Documents/
Do not use the Files application’s browser-navigation URL. Redirecting to an HTML login page is not a successful WebDAV connection.
Public shares use different endpoints and authentication rules. This article covers an authenticated user’s files, not public-link access.
2. Create an app password
In your personal security settings, create a device-specific app password for the integration. Use the username and generated password Nextcloud supplies.
This is especially important with two-factor authentication or an external identity provider. An interactive browser login or ordinary account password might not be usable by a background application.
Treat the app password as a credential for HTTP authentication, not as an OAuth bearer token. Use HTTPS, keep certificate validation enabled, and give the integration its own credential so you can revoke it independently.
For a local PowerShell session, prompt rather than placing the password in source code or command history:
$credential = Get-Credential -UserName "alice" -Message "Nextcloud app password"
$env:WEBDAV_ENDPOINT = "https://cloud.example.com/nextcloud/remote.php/dav/files/alice/Documents/"
$env:WEBDAV_USERNAME = $credential.UserName
$env:WEBDAV_PASSWORD = $credential.GetNetworkCredential().Password
$env:WEBDAV_REMOTE_FILE = "report + notes.txt"
$env:WEBDAV_OUTPUT = ".\nextcloud-download.txt"
Environment variables are convenient process configuration, not a secret vault. In a deployed service, obtain the app password from your secret-management system. Do not log it or include it in an endpoint URL.
3. List the collection and download a file
Create a console application and install the package:
dotnet new console -n NextcloudFiles --framework net8.0
Set-Location NextcloudFiles
dotnet add package WebDAVClient --version 3.2.0
Use net10.0 instead if that is your application’s target. The commands above create a .NET 8 application; they do not require building the library from source.
Replace Program.cs with this complete program:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using WebDAVClient;
using WebDAVClient.Helpers;
try
{
string Required(string name) => Environment.GetEnvironmentVariable(name)
?? throw new ArgumentException("Set " + name + ".");
var endpoint = new Uri(Required("WEBDAV_ENDPOINT"));
if (endpoint.Scheme != Uri.UriSchemeHttps)
{
throw new ArgumentException("Use HTTPS for authenticated access.");
}
using var http = new HttpClient(new HttpClientHandler
{
Credentials = new NetworkCredential(
Required("WEBDAV_USERNAME"), Required("WEBDAV_PASSWORD")),
UseDefaultCredentials = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false
});
using var client = new DavClient(endpoint, http);
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(60));
var listing = await client.PropFindAsync(
DavPath.Root, new PropFindOptions { Depth = DavDepth.One }, deadline.Token);
listing.EnsureSucceeded();
foreach (var resource in listing.Resources)
{
Console.WriteLine(resource.Hrefs[0]);
}
var remoteFile = DavPath.FromSegments(Required("WEBDAV_REMOTE_FILE"));
using var download = await client.DownloadAsync(
remoteFile, cancellationToken: deadline.Token);
download.EnsureHttpSuccess();
var content = download.Content
?? throw new IOException("The response contains no downloadable content.");
var output = Environment.GetEnvironmentVariable("WEBDAV_OUTPUT")
?? "nextcloud-download.txt";
await using (var file = new FileStream(
output, FileMode.CreateNew, FileAccess.Write))
{
await content.CopyToAsync(file, deadline.Token);
}
Console.WriteLine("Download complete.");
return 0;
}
catch (WebDAVException ex)
{
Console.Error.WriteLine(
$"WebDAV failed: HTTP {ex.ResponseStatusCode ?? ex.GetHttpCode()}, member {ex.FailedStatusCode}.");
return 1;
}
catch (OperationCanceledException)
{
Console.Error.WriteLine("Cancelled or timed out; inspect any partial local file.");
return 130;
}
catch (Exception ex) when (ex is HttpRequestException || ex is IOException
|| ex is InvalidDataException || ex is UnauthorizedAccessException
|| ex is ArgumentException || ex is UriFormatException)
{
Console.Error.WriteLine($"Configuration, transport or local-file failure: {ex.GetType().Name}.");
return 1;
}
Run it in the session where you set the environment variables:
try {
dotnet run
if ($LASTEXITCODE -ne 0) {
throw "Nextcloud download failed; inspect the reported error."
}
} finally {
Remove-Item Env:\WEBDAV_PASSWORD -ErrorAction SilentlyContinue
$credential = $null
}
This program reads remote storage; it does not upload, rename, or delete anything on Nextcloud. It creates a new local file and refuses to overwrite an existing one.
The listing includes the addressed collection as well as its immediate children. It is not a recursive walk. The code prints hrefs for inspection, but downloads the explicitly configured filename rather than blindly following a returned href.
Why these choices matter
DavPath.FromSegments takes unescaped path segments. Pass "report + notes.txt", not a pre-encoded "report%20%2B%20notes.txt". For a nested path, pass each segment separately, such as DavPath.FromSegments("Documents", "report + notes.txt"), relative to the configured collection.
Automatic redirects are disabled so a mistaken endpoint does not silently become a different request target. If your service redirects, configure the final authorized HTTPS WebDAV endpoint instead of switching redirects on to hide the problem.
The injected HttpClient remains caller-owned. This short program disposes both objects when it exits; a long-running application should reuse its transport rather than create one for every file.
The download result owns its HTTP response. Keep it alive while copying the body, dispose it afterward, and pass the cancellation token to the body copy as well as the request.
4. Treat metadata as data with its own status
WebDAV listings normally arrive as 207 Multi-Status. That means “inspect the enclosed results,” not “every property succeeded.”
For the basic example, EnsureSucceeded() applies a strict success check. If you request optional vendor properties, inspect their individual outcomes instead of treating an unavailable property as an invented value.
Nextcloud uses both standard DAV properties and vendor namespaces:
| Property | Useful for |
|---|---|
DAV:resourcetype | Distinguishing collections from ordinary resources |
DAV:getetag | Carrying an opaque representation validator |
{http://owncloud.org/ns}fileid | Reading a server-local file identifier |
{http://owncloud.org/ns}permissions | Reading reported permissions, not proving authorization |
{http://nextcloud.org/ns}has-preview | Finding out whether preview metadata is available |
The owncloud.org namespace is intentional even on Nextcloud. Match the namespace URI and local name, not a particular XML prefix such as oc.
An optional property’s 404 does not mean the file itself is missing. Likewise, a property-level 403 is not fixed by seeing an outer 207.
The repository’s Nextcloud executable example selects vendor properties and uses a streamed depth-one listing. Its listing implementation preserves property statuses, allows unavailable optional properties, and requires the listing to finish before continuing.
That completion requirement matters for file indexes. A cancelled or truncated listing is not evidence that unseen remote files were deleted.
5. Diagnose the right failure
| Symptom | What to investigate |
|---|---|
| 401 | Incorrect or revoked app password; wrong supplied username |
| 403 | Collection/file permissions, server policy, or proxy restrictions |
| 404 | User ID, installation prefix, endpoint, and exact filename |
| Redirect or HTML instead of DAV XML | Browser URL or authentication gateway instead of the final WebDAV endpoint |
| TLS failure | Certificate hostname, expiry, and trusted CA configuration |
| Local I/O failure | Existing output file, permissions, missing parent directory, or free space |
Do not disable TLS verification to make an authentication example work. Fix the trust configuration.
In the modern API, well-formed server rejections are inspectable results; the Ensure... calls above turn the relevant failures into exceptions. Transport failures and malformed protocol responses can throw before a result is returned.
Use structured, access-controlled diagnostics in production. HTTP/member status and an application correlation ID are usually safer starting points than raw bodies, authorization headers, or user filenames.
A plain download is not a resumable download
If the body copy fails, the new local file can contain only part of the response. The next run deliberately refuses to overwrite it. This example has no checkpoint and does not append to that file.
For large transfers that need restart recovery, use the separate resumable-download workflow, introduced in 3.1. It requires a stable strong ETag and suitable response metadata. Validate those requirements against your actual server; a successful small download alone does not establish resumability.
Run the maintained example
If you prefer a checked-in application to a pasted program, clone the repository, configure the same variables, and run these commands from the repository root:
$env:WEBDAV_AUTH = "credentials"
dotnet run --project examples\WebDAVClient.Examples -f net10.0 -- nextcloud --validate
if ($LASTEXITCODE -ne 0) {
throw "Configuration validation failed."
}
dotnet run --project examples\WebDAVClient.Examples -f net10.0 -- nextcloud
Use an installed .NET 10 SDK for the current multi-targeted checkout; -f net8.0 selects execution on .NET 8 when that runtime is also installed. Supply the app-password variables again if you already cleared them, and clear the password afterward.
--validate is offline: it checks endpoint/authentication configuration, not credentials against the server, file existence, or download permissions. The live command uses the example host’s 60-second budget.
Start with a test account and one existing file. Once listing, metadata, and downloading behave as expected, connect those operations to your application’s storage abstraction.
Use the Nextcloud integration guide and its executable host as the next step. They document the configuration and failure behavior alongside the code.