This is a thin .NET client for Draftable's document comparison API.
It wraps the available endpoints, and handles authentication and signing for you.
The library is available on NuGet as DraftableCompareAPI.Client
.
The examples in this README are all in C#, but any CLR-based language (e.g. F#, VB.NET) is supported.
See the full API documentation for an introduction to the API, usage notes, and other references.
-
Sign up for free at api.draftable.com to get your credentials.
-
Install the
Draftable.CompareAPI.Client
NuGet package. This will add a reference toDraftable.CompareAPI.Client
. -
Start creating comparisons:
using (var comparisons = new Comparisons("your account id", "your auth token")) { var comparison = comparisons.Create( Comparisons.Side.FromURL("https://api.draftable.com/static/test-documents/paper/left.pdf", "pdf"), Comparisons.Side.FromURL("https://api.draftable.com/static/test-documents/paper/right.pdf", "pdf") ); var viewerURL = comparisons.SignedViewerURL(comparison.Identifier, validFor: TimeSpan.FromMinutes(30)); Console.WriteLine("Comparison created:"); Console.WriteLine(comparison); Console.WriteLine(); Console.WriteLine($"Viewer URL (expires in 30 min): {viewerURL}"); }
The client depends on the Newtonsoft.Json
NuGet package for serialization.
The client is built against version 4.5.2 of the .NET framework, so framework versions 4.5.2 and higher are supported.
All requests can be made synchronously or asynchronously (using the methods suffixed with Async
).
All asynchronous methods return Task
s that, when awaited, will complete succesfully or throw an exception, just like their synchronous counterparts.
The API is designed such that requests should always succeed and comparisons should always succeed in production. This means:
- Exceptions when making requests will only occur upon network failure, or when you provide invalid credentials or data.
- Comparisons will only fail when the files are unreadable, or exceed your account's size limits.
When making method calls, parameters are immediately validated, and ArgumentNullException
s and ArgumentOutOfRangeException
s will be thrown
if you provide invalid parameters. Otherwise, all possible exceptions are documented for every method in the client library.
If you Dispose()
the Comparisons
client, further requests will throw an ObjectDisposedException
, and any requests in progress will be canceled,
throwing an OperationCanceledException
.
The API client class, Comparisons
, is completely thread-safe.
The library provides a namespace Draftable.CompareAPI
with two classes, Comparisons
and Comparison
.
To construct an API client, use new Comparisons(string accountId, string authToken)
.
The Comparisons
instance lets you manage your account's comparisons (creating new comparisons, and getting/deleting existing comparisons).
Instances of Comparison
are returned by API methods, and provide metadata for a given comparison.
Note: If you need to customize how HTTP requests are handled (e.g. to use a proxy server), you can use a constructor overload that allows you to configure
the underlying System.Net.Http.HttpClientHandler
used internally.
Comparisons
is disposable. Calling .Dispose()
will close any underlying connections and otherwise clean up the HTTP communication layer.
So, we'll assume you set things up as follows:
using Draftable.CompareAPI;
...
var comparisons = new Comparisons(<your account ID>, <your auth token>);
Comparisons
provides .GetAll()
and .Get(string identifier)
.
.GetAll()
returns aList<Comparison>
giving metadata for all your comparisons, ordered from newest to oldest. This is a potentially expensive operation..Get(string identifier)
returns a singleComparison
object, or raisesComparisons.NotFoundException
if there isn't a comparison with that identifier.
Comparison
objects have the following read-only properties:
Identifier
: a string giving the identifier.Left
,Right
:Comparison.Side
objects giving information about each side, with properties:FileType
: the file extension.SourceURL
(optional): if the file was specified as a URL, this will be a string with the URL. Otherwise,null
.DisplayName
(optional): the display name, if one was given. Otherwise,null
.
IsPublic
: a boolean giving whether the comparison is public, or requires authentication to view.CreationTime
: aDateTime
giving when the comparison was created.ExpiryTime
(optional): if the comparison will expire, anDateTime
giving the expiry time. Otherwise,null
(indicating no expiry).Ready
: boolean indicating whether the comparison is ready for display.
If a Comparison
is Ready
(i.e. it has been processed and is ready for display), it the following additional properties will be non-null:
ReadyTime
: aDateTime
giving the time the comparison became ready.Failed
: a boolean indicating whether the comparison succeeded or failed.ErrorMessage
(only present ifFailed
): a string providing the developer with the reason the comparison failed.
string identifier = "<identifier>";
try {
var comparison = comparisons.Get(identifier);
Debug.Assert(comparison.Identifier == identifier);
Console.WriteLine(
"Comparison '{0}' ({1}) is {2}.",
identifier,
comparison.IsPublic ? "public" : "private",
comparison.Ready ? "ready" : "not ready"
);
if (comparison.Ready) {
Console.WriteLine(
"The comparison took {0} seconds.",
(comparison.ReadyTime.Value - comparison.CreationTime).TotalSeconds
);
if (comparison.Failed.Value) {
Console.WriteLine("The comparison failed. Error message: {0}", comparison.ErrorMessage);
}
}
} catch (Comparisons.NotFoundException) {
Console.WriteLine("Comparison '{0}' does not exist.", identifier);
}
Comparisons
provides .Delete(string identifier)
, which attempts to delete the comparison with that identifier.
It has no return value, and raises Comparisons.NotFoundException
if there isn't a comparison with that identifier.
var allComparisons = comparisons.GetAll();
var oldestComparisons = allComparisons.OrderBy(comparison => comparison.CreationTime).Take(10).ToList();
Console.WriteLine("Deleting oldest {0} comparisons...", oldestComparisons.Count);
foreach (var comparison in oldestComparisons) {
comparisons.Delete(comparison.Identifier);
Console.WriteLine("Deleted comparison '{0}'.", comparison.Identifier);
}
Comparisons
provides .Create(left, right, [identifier], [isPublic], [expires])
, which returns
a Comparison
object representing the newly created comparison.
.Create(...)
accepts the following arguments:
left
,right
:Comparisons.Side
objects describing the left and right files. These are described below.identifier
(optional): the identifier to use for the comparison.- If specified, the identifier can't clash with an existing comparison. (If so, a
Comparisons.BadRequestException
is thrown.) - If left unspecified, the API will automatically generate one for you.
- If specified, the identifier can't clash with an existing comparison. (If so, a
isPublic
(optional): whether the comparison is publicly accessible.- Defaults to
false
. Iftrue
, then the comparison viewer can be accessed by anyone, without authentication. - See the full API documentation for details.
- Defaults to
expires
(optional): an optionalTimeSpan
specifying when the comparison will be automatically deleted.- If given, the
TimeSpan
must be positive. - Defaults to
null
, meaning the comparison will never expire.
- If given, the
To specify left
and right
, create Comparisons.Side
instances using one of the static constructors.
The full set of overloads are documented in the xml docs, but here are the main ones:
-
Comparisons.Side.FromURL(sourceURL, fileType, [displayName])
- Specifies a file via a URL. You must give a fully qualified URL from which Draftable can download the file.
fileType
is required, given as the file extensiondisplayName
is an optional name for the file, to be shown in the comparison
-
Comparisons.Side.FromFile(fileStream, fileType, [displayName])
- Specifies a file to be uploaded in the request. You can provide the file as a stream, byte array, or via a file path.
fileType
anddisplayName
are as before.
The following file types are supported:
- PDF:
pdf
- Word:
docx
,docm
,doc
,rtf
- PowerPoint:
pptx
,pptm
,ppt
If you try to create a Comparisons.Side
with an invalid fileType
or malformed url
, an ArgumentOutOfRangeException
will be immediately thrown.
Exceptions are raised by .Create(...)
if a parameter is invalid (e.g. expires
is set to a time in the past).
The method will either immediately throw an ArgumentOutOfRangeException
, or a Comparisons.BadRequestException
will be thrown after communication with the API.
- Most parameters will be validated client-side by the library, in which case an
ArgumentOutOfRangeException
is thrown. - If you provide an invalid parameter that isn't validated client-side (e.g. an
identifier
that is already in use by another comparison) then the POST request will fail and aComparisons.BadRequestException
will be thrown.
var comparison = comparisons.Create(
Comparisons.Side.FromURL("https://domain.com/path/to/left.pdf", "pdf"),
Comparisons.Side.FromFile("path/to/right/file.pdf"),
// identifier: not specified, so Draftable will generate one
identifier: null,
// isPublic: false, so that the comparison is private
isPublic: false,
// expires: 30 minutes in the future, so the comparison will be automatically deleted then
expires: TimeSpan.FromMinutes(30)
);
Console.WriteLine("Created comparison:");
Console.WriteLine(comparison);
// This generates a signed viewer URL that can be used to access the private comparison for the next 10 minutes.
var viewerURL = comparisons.SignedViewerURL(
// identifier: The identifier of the comparison
identifier: comparison.identifier,
// validFor: The amount of time before the link expires
validFor: TimeSpan.FromMinutes(10),
// wait: Whether the viewer should wait for a comparison with the given identifier to exist.
// (This is simply `false` for normal usage.)
wait: false
);
Console.WriteLine("Viewer URL (expires in 10 min): {0}", viewerURL);
Comparisons are displayed using a viewer URL. See the section on displaying comparisons in the API documentation for details.
Viewer URLs are generated with the following methods:
-
comparisons.PublicViewerURL(string identifier, bool wait = false)
- Viewer URL for a public comparison with the given
identifier
. wait
isfalse
by default, meaning the viewer will 404 and show an error if no such comparison exists.- If
wait
istrue
, the viewer will wait for a comparison with the givenidentifier
to exist (potentially displaying a loading animation forever).
- Viewer URL for a public comparison with the given
-
comparisons.SignedViewerURL(String identifier, [TimeSpan validFor], [boolean wait])
- Gets a signed viewer URL for a comparison with the given
identifier
. (The signature is an HMAC based on your credentials.) validFor
gives when the URL will expire.- If
validFor
isn't specified, the URL defaults to expiring 30 minutes in the future (more than enough time to load the page).
- If
- Again, if
wait
istrue
, the viewer will wait for a comparison with the givenidentifier
to exist.
- Gets a signed viewer URL for a comparison with the given
In this example, we'll start creating a comparison in the background, but immediately direct our user to a viewer. The comparison viewer will display a loading animation, waiting for the comparison to be created and processed.
// This generates a unique identifier we can use.
var identifier = Comparisons.GenerateIdentifier();
var createComparisonTask = comparisons.CreateAsync(
Side.FromURL("https://api.draftable.com/static/test-documents/code-of-conduct/left.rtf", "rtf"),
Side.FromURL("https://api.draftable.com/static/test-documents/code-of-conduct/right.pdf", "pdf"),
// identifier: specify the identifier we just generated
identifier: identifier
);
// At some point, we will have created the comparison.
// (The operation could take some time if we're uploading files.)
// In the mean time, we can immediately give the user a viewer URL, using `wait=true`:
string viewerURL = comparisons.SignedViewerURL(identifier, TimeSpan.FromMinutes(30), wait: true);
// This URL is valid for 30 minutes, and will show a loading screen until the comparison is ready.
Console.WriteLine("Comparison is being created. View it here: {0}", viewerURL);
// For the purposes of this example, we'll just block until the request finishes.
var comparison = createComparisonTask.ConfigureAwait(false).GetAwaiter().GetResult();
// More generally, the async/await pattern is recommended:
// var comparison = await createComparisonTask;
Comparisons.GenerateIdentifier()
generates a random unique identifier for you to use.
By default, the client library respects <system.net>...</system.net>
settings in your app's configuration file, as well as any system-wide internet settings (e.g. proxy server) set in Internet Options
.
If you need to customize request settings, you can add settings to application config (e.g. see this MSDN page for proxy configuration). Alternatively, you can use a constructor for Comparisons
that takes in a configuration callback.
The configuration callback is an Action<HttpClientHandler>
that can perform any necessary configuration of the client library's underlying HttpClientHandler
, including setting proxy settings, timeouts, or other request parameters.
That's it! Please report issues you encounter, and we'll work quickly to resolve them. Contact us at support@draftable.com if you need assistance.