SharpOnvifServer 0.10.0

dotnet add package SharpOnvifServer --version 0.10.0
                    
NuGet\Install-Package SharpOnvifServer -Version 0.10.0
                    
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="SharpOnvifServer" Version="0.10.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SharpOnvifServer" Version="0.10.0" />
                    
Directory.Packages.props
<PackageReference Include="SharpOnvifServer" />
                    
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 SharpOnvifServer --version 0.10.0
                    
#r "nuget: SharpOnvifServer, 0.10.0"
                    
#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 SharpOnvifServer@0.10.0
                    
#: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=SharpOnvifServer&version=0.10.0
                    
Install as a Cake Addin
#tool nuget:?package=SharpOnvifServer&version=0.10.0
                    
Install as a Cake Tool

SharpOnvif

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

Upgrading from 0.9.x? 0.10.0 replaces WCF and CoreWCF with HttpClient and ASP.NET Core, and merges the 50 per-service packages into two. Nothing changed on the wire. See doc/migration.md.

SharpOnvifServer

Onvif server provides NET8 and NET10 bindings generated from the Onvif WSDLs by WsdlGenerator, hosted on ASP.NET Core. It makes it easy to implement only parts of the Onvif specification needed for your project.

NuGet version

Start with a normal ASP.NET Core application:

var builder = WebApplication.CreateBuilder();

Add Digest authentication for Onvif:

builder.Services.AddSingleton<IUserRepository, UserRepository>();
builder.Services.AddOnvifDigestAuthentication();

Implement IUserRepository to provide user verification and configure your user:

public class UserRepository : IUserRepository
{
    public string UserName { get; set; } = "admin";
    public string Password { get; set; } = "password";

    public Task<UserInfo> GetUser(string userName)
    {
        if (string.Compare(userName, UserName, false) == 0)
        {
            return Task.FromResult(new UserInfo() { UserName = userName, Password = Password });
        }

        return Task.FromResult((UserInfo)null);
    }

    public Task<UserInfo> GetUserAsync(string userName)
    {
        return Task.FromResult(GetUser(userName));
    }

    // used only when userhash=TRUE - see the examples for an implementation
    public UserInfo GetUserByHash(string algorithm, string userName, string realm)
    {
        throw new NotImplementedException();
    }

    public Task<UserInfo> GetUserByHashAsync(string algorithm, string userName, string realm)
    {
        throw new NotImplementedException();
    }
}

Optionally, add Onvif discovery to make your service discoverable on the network:

builder.Services.AddOnvifDiscovery();

The device answers a Probe, and announces itself with a WS-Discovery Hello when it starts and a Bye when it stops, so a client learns about it without having to probe. Every announcement names the same endpoint reference, which is how a client pairs the Bye with the device that said Hello. That address lasts as long as the process unless you give it one that outlives a restart:

builder.Services.AddOnvifDiscovery(new OnvifDiscoveryOptions
{
    // From something the device keeps - its serial number or MAC - or a client sees a new
    // device every time this one is restarted.
    EndpointReference = "urn:uuid:" + deviceUuid,
});

Simple DeviceImpl just extends SharpOnvifServer.DeviceMgmt.DeviceBase and overrides a method you want to implement - for instance GetDeviceInformation:

public class DeviceImpl : DeviceBase
{
    public override GetDeviceInformationResponse GetDeviceInformation(GetDeviceInformationRequest request)
    {
        return new GetDeviceInformationResponse()
        {
            FirmwareVersion = "1.0",
            HardwareId = "1.0",
            Manufacturer = "Manufacturer",
            Model = "1",
            SerialNumber = "1"
        };
    }
}

Each operation appears three times on the generated base, each layer defaulting to the next, so you can override whichever suits: an async form taking the request, a synchronous form taking the request, and a synchronous form taking the request's members as arguments. Anything you do not override is reported to the client as the ter:ActionNotSupported fault.

Add it as a singleton:

builder.Services.AddSingleton<DeviceImpl>();

Add authentication:

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

Finally map the service onto a URL and run:

app.MapOnvifService<DeviceImpl>("/onvif/device_service");
app.Run();

Several services can share one URL, which is what real devices do - requests are routed by their SOAP action:

app.MapOnvifService<DeviceImpl>("/onvif/device_service");
app.MapOnvifService<MediaImpl>("/onvif/device_service");
app.MapOnvifService<PTZImpl>("/onvif/device_service");

The operation is found from the action parameter of the Content-Type header, a wsa:Action SOAP header, or the body element, whichever the client sends - Onvif Device Manager uses the second form for event subscriptions.

An address with a trailing segment reaches the same service, which is how Onvif addresses a subscription manager: a request to /onvif/Events/PullPointSubscription/aV9xN2sMv1Qb0Zt8/ reaches the service mapped at /onvif/Events/PullPointSubscription, with the segment available to the implementation as HttpContext.Items[OnvifEvents.ONVIF_SUBSCRIPTION_ID] - a string, because a subscription ID is an unguessable token rather than a counter. Your Onvif service should now be discoverable on the network and you should be able to use Onvif Device Manager or similar tool to call your endpoint. See Onvif.Server sample project for a complete example.

SharpOnvifClient

Onvif client provides .NET Standard 2.0, .NET Framework 4.8.1, NET8.0 and NET10.0 bindings generated from the Onvif WSDLs by WsdlGenerator, over HttpClient. SimpleOnvifClient wraps common API calls to get basic information from the camera and includes both Pull Point as well as Basic event subscriptions.

NuGet version

To discover Onvif devices on your network, use:

var discovery = new OnvifDiscoveryClient();
var onvifDevices = await discovery.DiscoverAsync();

A Probe only finds what is on the network at the moment it is sent, which is no help to an application that starts before its camera does. To wait for one to appear instead:

var device = await discovery.WaitForDeviceAsync(cancellationToken: stopping.Token);

It listens for the WS-Discovery Hello a device sends when it joins, and keeps probing as well - an announcement is UDP multicast, so it can be lost, and one sent before you started listening is already gone. Pass a predicate to wait for a particular device. There is no timeout, because "wait until the camera is switched on" has no natural one; cancel the token to stop.

Both listen on every interface that carries multicast, IPv4 and IPv6 alike.

OnvifDiscoveryListener is the same mechanism without the waiting, for an application that wants to keep track of devices coming and going:

var listener = new OnvifDiscoveryListener();
listener.DeviceAnnounced += (s, e) => Console.WriteLine($"{e.Device.Name} arrived");
listener.DeviceLeft += (s, e) => Console.WriteLine($"{e.Device.Name} left");
listener.Start();

To create the SimpleOnvifClient, use:

var client = new SimpleOnvifClient(onvifDevices[0].Addresses[0], "admin", "password");

Call GetDeviceInformationAsync to retrieve information about the device:

var deviceInfo = await client.GetDeviceInformationAsync();

Call GetServicesAsync to retrieve a list of all services supported by the device:

var services = await client.GetServicesAsync();

Some operations require the device to support a service. For instance, to retrieve the stream URI the device must support the media service. To check whether the Onvif service is supported by the device, call:

if (services.Service.FirstOrDefault(x => x.Namespace == OnvifServices.MEDIA) != null)
{
    // operation only available when the service is supported
}

Full list of services that can be supported by the device is available in SharpOnvifCommon.OnvifServices.

Delivering only what a subscriber asked for

A client subscribing to events says which it wants, and a device that sends it everything else as well is not conformant. TopicFilter reads the filter out of the subscribe request and answers whether a notification is one of them:

TopicFilter topics = TopicFilter.FromFilter(request.Filter);
...
if (topics.Matches(notification))
{
    // queue it for this subscriber
}

It understands a concrete topic, a set of them separated by |, * for one level, and a trailing //. for a topic and everything beneath it. An expression written in a dialect it cannot evaluate matches everything, so a subscriber is never silently sent nothing.

A notification carries name/value pairs as tt:SimpleItem. Anything with a shape to it - a rectangle, an analytics payload - goes in SourceElements or DataElements and is written as tt:ElementItem:

var message = new NotificationMessage
{
    Topic = "RuleEngine/CellMotionDetector/Motion",
    Data = { { "IsMotion", "true" } },
    DataElements = { { "Shape", rectangleElement } },
};

Pull Point event subscription

Pull point event subscription does not require any special networking configuration and it should work in most networks. To create a new Pull Point subscription, call:

var subscription = await client.PullPointSubscribeAsync();

To retrieve the current notifications from the Pull Point subscription, call:

var notifications = await client.PullPointPullMessagesAsync(subscription);
foreach (var notification in notifications)
{
    // handle the notification message
    bool? isMotion = SharpOnvifClient.OnvifEvents.IsMotionDetected(notification);
}

Basic event subscription

Basic event subscription utilizes a callback from the camera when an event occurs. This requires the camera to be able to reach your machine through a firewall/NAT. To listen for incoming notifications, you must run SimpleOnvifEventListener:

// ID 1 will identify this camera in the callback
const int CAMERA1 = 1;

var eventListener = new SimpleOnvifEventListener();
eventListener.Start((int cameraID, string ev) =>
{
    bool? isTamper = SharpOnvifClient.OnvifEvents.IsTamperDetected(ev);
    if(cameraID == CAMERA1)
    {
        // handle the notification message for CAMERA1
    }
});

var subscriptionResponse = await client.BasicSubscribeAsync(eventListener.GetOnvifEventListenerUri(CAMERA1));

Using the generated clients

Every Onvif service is in the SharpOnvifClient package, each under its own namespace (SharpOnvifClient.DeviceMgmt, SharpOnvifClient.Media, SharpOnvifClient.PTZ, and so on), with the shared Onvif data model in SharpOnvifCommon.Onvif. Create the client with the endpoint address and, if the device requires them, credentials:

using (var deviceClient = new SharpOnvifClient.DeviceMgmt.DeviceClient(
    "http://192.168.1.10/onvif/device_service", "admin", "password"))
{
    var deviceInfo = await deviceClient.GetDeviceInformationAsync(new GetDeviceInformationRequest());
}

Both Onvif digest schemes are offered by default. For full control over authentication and transport, pass OnvifClientSettings:

var settings = new SharpOnvifCommon.Soap.OnvifClientSettings
{
    Credentials = new System.Net.NetworkCredential("admin", "password"),
    Authentication = new SharpOnvifCommon.Security.OnvifAuthenticationSettings(
        SharpOnvifCommon.Security.DigestAuthentication.HttpDigest),
    Timeout = TimeSpan.FromSeconds(30),
};

using (var deviceClient = new SharpOnvifClient.DeviceMgmt.DeviceClient(uri, settings))
{
    var deviceInfo = await deviceClient.GetDeviceInformationAsync(new GetDeviceInformationRequest());
}

Every operation also has an overload that takes the request's members directly, so you rarely need to build the request yourself:

var services = await deviceClient.GetServicesAsync(includeCapability: false);

A device that cannot be reached, drops the connection, or does not answer in time raises SharpOnvifCommon.Soap.SoapTransportException. A device is a thing that reboots and loses power, and a pull point spends nearly all its time waiting on a request that any of those cuts short, so a loop that polls one has to expect it:

while (true)
{
    var subscription = await client.PullPointSubscribeAsync(60);
    try
    {
        while (true)
        {
            var messages = await client.PullPointPullMessagesAsync(
                subscription.SubscriptionReference.Address.Value);
            // handle the notifications
        }
    }
    catch (SoapTransportException)
    {
        // the device went away; it has forgotten the subscription, so make a new one
    }
}

TimedOut tells a device that ran late from one that was not there at all, and InnerException carries what the HTTP stack actually said. A cancellation you asked for is not this - that still arrives as an OperationCanceledException.

A device that answers with a SOAP fault raises SharpOnvifCommon.Xml.SoapFaultException, which carries the Onvif error subcode:

try
{
    await deviceClient.GetHostnameAsync();
}
catch (SoapFaultException fault) when (fault.Fault?.Subcode == "ActionNotSupported")
{
    // the device does not implement this operation
}

See Onvif.Client sample project for a complete example.

Digest authentication

Onvif supports two types of Digest authentication. Legacy WS-UsernameToken authentication carried inside the SOAP headers and HTTP Digest authentication as defined in RFC 7616. Both types of authentication are now supported on both the client and the server.

Load balancing

HTTP Digest keeps two pieces of state on the server, and both are per process by default: the private key and the record of which nonces have been seen. To run this service behind a load balancer, you will have to store the private key and replay list in a shared location. Then you can configure the service as follows:

HttpDigestAuthentication.SetNoncePrivateKey(keyFromYourSecretStore);

builder.Services.AddOnvifDigestAuthentication(options =>
{
    options.HttpDigestNonceReplayStore = new MyDistributedNonceReplayStore();
});

A device holds at most DefaultEventSubscriptionManager.MaxSubscriptions (1000) at once and refuses the next with a fault. A subscription outlives the request that made it and is swept only when it expires, so without a limit a client subscribing in a loop - broken as easily as hostile - leaves a device holding every one it asked for.

INonceReplayStore has one method - it spends a nonce at a nonce count and says whether that count had been seen before. MemoryNonceReplayStore, the default, holds the record in this process.

Logging

The client reports what it could not do through an ILog it is given. The logger belongs to the object, not to the process, so an application watching several cameras can tell which one is complaining - or send one of them nowhere:

var logger = new DefaultOnvifLogger { IsLoggingEnabled = true, IsDebugEnabled = false };

var client = new SimpleOnvifClient(uri, "admin", "password") { Logger = logger };
var listener = new SimpleOnvifEventListener(host) { Logger = logger };
var discovery = new OnvifDiscoveryClient(logger);

OnvifClientSettings.Logger does the same for a service client built directly. Given none, an object reports nowhere. Implement ILog to send it into your own logging, or use NullOnvifLogger.Instance to be explicit about silence. There is no dependency on any logging package, which is what keeps these assemblies free of dependencies altogether. The server does not use this - it is given an ILogger by the host and logs to that, and the two names are kept apart deliberately so that one does not shadow the other where both are in scope.

No dependencies

SharpOnvifClient, SharpOnvifServer and SharpOnvifCommon reference no NuGet packages on any of their target frameworks.

Testing

Only the DeviceMgmt, Media and Events were tested with Hikvision cameras. Server implementation was tested using Onvif Device Manager.

Credits

Special thanks to Piotr Stapp for figuring out the SOAP security headers in NET8: https://stapp.space/using-soap-security-in-dotnet-core/.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (25)

Showing the top 5 NuGet packages that depend on SharpOnvifServer:

Package Downloads
SharpOnvifServer.DeviceMgmt

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

SharpOnvifServer.Media

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

SharpOnvifServer.PTZ

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

SharpOnvifServer.Events

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

SharpOnvifServer.Imaging

A C# implementation of the Onvif interface - client as well as the server. All profiles are supported.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.10.0 37 9/14/2026
0.9.6 297 9/10/2026
0.9.5 1,033 6/12/2026
0.9.4 588 6/10/2026
0.9.3 679 5/20/2026
0.9.2 721 4/11/2026
0.9.1 577 3/1/2026
0.9.0 2,616 2/12/2026
0.8.1 353 2/7/2026
0.8.0 323 2/1/2026
0.7.1 425 12/30/2025
0.7.0 413 11/15/2025
0.6.1 728 11/13/2025
0.6.0 572 11/7/2025
0.5.2 669 10/27/2025
0.5.1 648 10/26/2025
0.5.0 1,150 10/25/2025
0.4.3 550 10/25/2025
0.4.2 691 8/27/2025
0.4.1 632 7/12/2025
Loading failed