How to upgrade the .NET SDK from v1 to v2
This guide will help you navigate through changes included in version 2 of the Account Protect .NET SDK for a successful upgrade.
Version 2 is generated from the Account Protect OpenAPI specification, which changes the client, event, and response types throughout the SDK.
Documentation for version2.x.xRefer to the new documentation for detailed instructions on how to use version 2 of the SDK.
Overview of changes
- Update client setup to a builder pattern with
Client.Builder(...), replacingAddDataDomedependency injection registration andDataDomeOptions. - Replace
IDataDomeContextand itsValidate/Collectmethods with one dedicated class per operation, such asValidateLoginandCollectAccountUpdate. - Replace the shared
DataDomeEventbuilder with per-event payload builders. - Validate the payload at build time and throw a
ValidationExceptionon missing required fields. - Add a
RequestMetadataobject, passed alongside the request, to override the host, protocol, and forwarded client IP the SDK reads from the request (see Step 7). - Replace the response
Statusfield with exceptions: validate calls still fail open and returnAllow, while collect and feedback calls now throwApiException,HttpException, orRequestTimeoutException. - Remove the
StatusandIPfields from the response model. - Remove asynchronous validate and collect calls. Wrap the synchronous
Perform(...)call yourself if you need non-blocking behavior (see Step 6). - Add support for custom events with
ValidateCustomandCollectCustom. - Add support for the feedback endpoint.
- Add support for .NET 8.0.
Migration steps
Step 1: Upgrade to the latest package version
dotnet add package DataDome.AspNetCore.Fraud.SDK --version 2.0.0Step 2: Update the client instantiation
Replace AddDataDome and DataDomeOptions with a Client built by Client.Builder(...) and registered as a singleton:
using DataDome.AspNetCore.Fraud.SDK;
var appBuilder = WebApplication.CreateBuilder(args);
appBuilder.Services.AddSingleton(_ => Client.Builder("your-api-key").Build());
var app = appBuilder.Build();using DataDome.AspNetCore.Fraud.SDK;
var builder = WebApplication.CreateBuilder(args);
// Option 1: Passing in a reference to the configuration, for values in appsettings
builder.Services.AddDataDome(builder.Configuration);
// Option 2: Passing in the values directly, for environment variables
builder.Services.AddDataDome(o =>
{
o.FraudAPIKey = "your-api-key";
});Inject the concrete Client type instead of IDataDomeContext:
using DataDome.AspNetCore.Fraud.SDK;
public LoginController(Client client)
{
_client = client;
}using DataDome.AspNetCore.Fraud.SDK.Model.Shared;
public LoginController(SDK.IDataDomeContext dataDome)
{
_dataDome = dataDome;
}Step 3: Update event submission and payload construction
Replace IDataDomeContext.Validate/Collect with one dedicated class per operation. The table below maps every event to its v2 operations:
| Event | v1 event class | v2 validate operation | v2 collect operation |
|---|---|---|---|
| Login | LoginEvent | ValidateLogin | CollectLogin |
| Registration | RegistrationEvent | ValidateRegistration | CollectRegistration |
| Account update | AccountUpdateEvent | ValidateAccountUpdate | CollectAccountUpdate |
| Password update | PasswordUpdateEvent | ValidatePasswordUpdate | CollectPasswordUpdate |
| Custom event | (new in v2) | ValidateCustom | CollectCustom |
The example below migrates a login event. Registration, account update, and password update follow the same pattern.
var builder = new LoginPayload.Builder()
.Account(login)
.Status(LoginPayloadStatus.Succeeded)
.User(user)
.Authentication(authentication)
.Session(session);
var validate = new ValidateLogin(builder).Perform(client, ctx.Request, null);
if (validate?.Action == ResponseAction.Allow)
{
return Results.Ok();
}
return Results.Problem("denied by Account Protect API", statusCode: 403);var loginEvent = new LoginEvent(
login,
authentication: new Authentication { Mode = AuthenticationMode.Password, Type = AuthenticationType.Local },
user: new User { Id = login },
session: new Session { Id = Guid.NewGuid().ToString("D"), CreatedAt = DateTime.UtcNow });
var ddResponse = await _dataDome.Validate(Request, loginEvent);
if (ddResponse != null && ddResponse.Action == ResponseAction.Allow)
{
// allowed
}For a failed login, Perform is synchronous and wraps CollectLogin:
var builder = new LoginPayload.Builder()
.Account(login)
.Status(LoginPayloadStatus.Failed);
try
{
new CollectLogin(builder).Perform(client, ctx.Request, null);
}
catch (Exception ex)
{
Console.Error.WriteLine($"error during collection: {ex}");
}await _dataDome.Collect(Request,
new LoginEvent(login, LoginStatus.Failed, failReason: LoginFailReason.WrongPassword));Step 4: Update your models
Replace the shared Address, User, and Authentication models with per-event types, and replace array fields with List<T>:
var userAddress = new UserAllOfAddress { Name = "Élysée Palace", CountryCode = "FR" };
var user = new LoginPayloadAllOfUser
{
Id = "fake_user_id",
PictureUrls = new List<string> { "https://example.com/profile.jpg" },
ExternalUrls = new List<string> { "https://example.com" },
};var user = new User
{
Id = "fake_user_id",
PictureUrls = new[] { "https://example.com/profile.jpg" },
ExternalUrls = new[] { "https://example.com" },
};This table maps the renamed model types:
| v1 | v2 |
|---|---|
Address | UserAllOfAddress |
User | LoginPayloadAllOfUser, RegistrationPayloadAllOfUser, AccountUpdatePayloadAllOfUser, or PasswordUpdatePayloadAllOfUser, depending on the event |
Authentication | LoginPayloadAllOfAuthentication, RegistrationPayloadAllOfAuthentication, or AccountUpdatePayloadAllOfAuthentication, depending on the event |
Title = "mrs" (string) | Title = UserTitle.Mrs (enum) |
new CustomField("name", "value") { ... } | new CustomField { Name = "name", Value = "value", ... } |
Step 5: Update your response and error handling
Replace the response's ResponseAction property with Action, and remove reads of the removed Status and IP fields:
if (validate?.Action == ResponseAction.Allow)if (ddResponse != null && ddResponse.ResponseAction == ResponseAction.Allow)Wrap collect and feedback calls in a try/catch block: they now throw ApiException, HttpException, or RequestTimeoutException instead of returning a Status field. Validate calls still fail open and return Allow on error.
Step 6: Handle asynchronous calls yourself
Version 1's IDataDomeContext.Validate and .Collect returned Task<Response> and were always called with await. Version 2's Perform(client, request, requestMetadata) is synchronous: there's no PerformAsync.
If your application relies on non-blocking calls, wrap Perform(...) in a Task, for example with Task.Run(...):
var validate = await Task.Run(() => new ValidateLogin(builder).Perform(client, ctx.Request, null));var ddResponse = await _dataDome.Validate(Request, loginEvent);Step 7: Pass forwarded-proxy headers through RequestMetadata
Version 1 always read the host, protocol, and forwarded client IP directly from the incoming request, with no way to override them. If your application runs behind a reverse proxy or load balancer that doesn't already normalize these values before they reach your app (for example, via ASP.NET Core's Forwarded Headers Middleware), the SDK could end up validating the proxy's address instead of the client's.
Version 2 introduces RequestMetadata to fix this: pass it as the third argument to Perform(...), and each non-null property overrides the corresponding value the SDK would otherwise read from the request:
var metadata = new RequestMetadata()
.WithHost(ctx.Request.Headers["X-Forwarded-Host"])
.WithProtocol(ctx.Request.Headers["X-Forwarded-Proto"])
.WithXForwardedForIp(ctx.Request.Headers["X-Forwarded-For"]);
var validate = new ValidateLogin(builder).Perform(client, ctx.Request, metadata);There was no equivalent in version 1: the SDK always used request.Scheme and the Host/X-Forwarded-For headers as-is, so a reverse proxy that stripped or rewrote them would silently produce the wrong client IP or host.
Reference
Status and reason constants
| v1 | v2 |
|---|---|
LoginStatus.Failed / .Succeeded | LoginPayloadStatus.Failed / .Succeeded |
PasswordStatus.Succeeded / .Failed | PasswordUpdatePayloadStatus.Succeeded / .Failed |
Reason.UserUpdate | PasswordUpdatePayloadReason.UserUpdate |
Configuration changes
| v1 | v2 |
|---|---|
AddDataDome(...) / DataDomeOptions | Client.Builder(key) |
await _dataDome.Validate(...) / .Collect(...) | Synchronous Perform(...). Wrap it in a Task yourself for non-blocking calls (see Step 6). |
| No override for the host, protocol, or forwarded client IP | RequestMetadata (see Step 7) |
Response Status and IP fields | Removed |
Updated 8 days ago

