Skip to content

Commit

Permalink
Added support for API v2.3, including the ability to specify a comple…
Browse files Browse the repository at this point in the history
…tion callback URL and support for exporting validated entries in multiple output formats

Added support for .NET 6.0
Improved documentation in code
Improved README
  • Loading branch information
verifalia committed Dec 2, 2021
1 parent 8d693f0 commit 2d84ab6
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 47 deletions.
103 changes: 89 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Verifalia RESTful API - .NET SDK and helper library

[Verifalia][0] provides a simple HTTPS-based API for validating email addresses in real-time and checking whether they are deliverable or not; this SDK library integrates with Verifalia and allows to [verify email addresses][0] under the following platforms:

- .NET 5.0 ![new](https://img.shields.io/badge/new-green)
- .NET 5.0 and higher, including .NET 6.0 ![new](https://img.shields.io/badge/new-green)
- .NET Core 1.0 (and higher)
- .NET Framework 4.5 (and higher)
- .NET Standard 1.3 (and higher)
Expand Down Expand Up @@ -135,10 +135,10 @@ Here is how to do that:
var validation = await verifalia
.EmailValidations
.SubmitAsync(new[] {
"[email protected]",
"[email protected]",
"[email protected]"
});
"[email protected]",
"[email protected]",
"[email protected]"
});

Console.WriteLine("Job Id: {validation.Overview.Id}");
Console.WriteLine("Status: {validation.Overview.Status}");
Expand Down Expand Up @@ -173,18 +173,18 @@ And here is how to request the same job, asking the SDK to automatically wait fo
var validation = await verifalia
.EmailValidations
.GetAsync(Guid.Parse("290b5146-eeac-4a2b-a9c1-61c7e715f2e9"),
new WaitingStrategy(true));
new WaitingStrategy(true));
```

### How to submit a file for validation
### How to import and submit a file for validation

This library includes support for submitting and validating files with email addresses, including:

- plain text files (.txt), with one email address per line;
- comma-separated values (.csv), tab-separated values (.tsv) and other delimiter-separated values files;
- Microsoft Excel spreadsheets (.xls and .xlsx).
- **plain text files** (.txt), with one email address per line;
- **comma-separated values** (.csv), **tab-separated values** (.tsv) and other delimiter-separated values files;
- **Microsoft Excel spreadsheets** (.xls and .xlsx).

To submit and validate files, one can still use the SubmitAsync() method mentioned
To submit and validate files, one can still use the `SubmitAsync()` method mentioned
above, passing either a `Stream` or a `FileInfo` instance or just a `byte[]` with the
file content. Along with that, it is also possible to specify the eventual starting
and ending rows to process, the column, the sheet index, the line ending and the
Expand Down Expand Up @@ -220,25 +220,80 @@ MIME content type of the file, which is automatically determined from the file e
the event you pass a `FileInfo` instance:

```c#
Stream inputStream = ...;
Stream inputStream = ...; // TODO: Acquire the input data somehow
var validation = await verifalia
.EmailValidations
.SubmitAsync(inputStream,
MediaTypeHeaderValue.Parse(WellKnownMimeContentTypes.TextPlain)); // text/plain
```

### Completion callbacks ###

Along with each email validation job, it is possible to specify an URL which
Verifalia will invoke (POST) once the job completes: this URL must use the HTTPS or HTTP
scheme and be publicly accessible over the Internet.
To learn more about completion callbacks, please see https://verifalia.com/developers#email-validations-completion-callback

To specify a completion callback URL, pass either a `ValidationRequest` or a `FileValidationRequest`
to the `SubmitAsync()` method and set its `CompletionCallback` property accordingly, as shown
in the example below:

```c#
await verifalia
.EmailValidations
.SubmitAsync(new ValidationRequest(new[] { "[email protected]" })
{
CompletionCallback = new Uri("https://your-website-here/foo/bar"),
// TODO: Other settings, if needed
});
```

Note that completion callbacks are invoked asynchronously and it could take up to
several seconds for your callback URL to get invoked.

### How to export validated entries in different output formats ###

This library also allows to export the entries of a completed email validation
job in different output formats through the `ExportEntriesAsync()` method, with the goal of generating a human-readable representation
of the verification results.

> **WARNING**: While the output schema (columns / labels / data format) is fairly
> complete, you should always consider it as subject to change: use the `GetAsync()` / `GetEntriesAsync()`
> methods instead if you need to rely on a stable output schema.
Here is an example showing how to export a given email verification job as a comma-separated values (CSV) file:

```c#
// Exports the validated entries for the job in the CSV format
var exportedStream = await verifalia
.EmailValidations
.ExportEntriesAsync(new Guid("722c2fd8-8837-449f-ad24-0330c597c993"),
ExportedEntriesFormat.Csv);

// Creates the output file stream
var fileStream = new FileStream("my-list.csv", FileMode.Create);

// Copies the exported stream into the output file stream
await exportedStream.CopyToAsync(fileStream);
```

### Don't forget to clean up, when you are done ###

Verifalia automatically deletes completed jobs after 30 days since their completion: deleting completed jobs is a best practice, for privacy and security reasons. To do that, you can invoke the `DeleteAsync()` method passing the job Id you wish to get rid of:
Verifalia automatically deletes completed jobs after a configurable
data-retention period (minimum 5 minutes, maximum 30 days) but it is strongly advisable that
you delete your completed jobs as soon as possible, for privacy and security reasons. To do that, you can invoke the `DeleteAsync()` method passing the job Id you wish to get rid of:

```c#
await verifalia
.EmailValidations
.DeleteAsync(validation.Id);
```

Once deleted, a job is gone and there is no way to retrieve its email validation(s).
Once deleted, a job is gone and there is no way to retrieve its email validation results.

### Iterating over your email validation jobs ###

Expand Down Expand Up @@ -338,6 +393,26 @@ await foreach (var dailyUsage in dailyUsages)
This section lists the changelog for the current major version of the library: for older versions,
please see the [project releases](https://github.com/verifalia/verifalia-csharp-sdk/releases).

### v3.1

Released on December 2<sup>nd</sup>, 2021

- Added support for API v2.3, including the ability to specify a completion callback URL
and support for exporting validated entries in multiple output formats
- Added support for .NET 6.0
- Improved documentation in code

### v3.0

Released on January 22<sup>nd</sup>, 2021

- Breaking change: IRestClient.InvokeAsync() now accepts a factory of HttpContent, which allows to work-around an issue with certain versions of .NET Standard and .NET Framework. The issue has been fixed in .NET Core since then, but one of our dependencies targets .NET Standard.
If you don't implement or use IRestClient directly in your code (which should be super rare) then you will not be affected by this change.
- Fixed an issue which prevented MultiplexedRestClient to properly retry HTTP invocations on failures.
- Fixed an issue with IAsyncEnumerable support on .NET Core 3.1 (was mistakenly disabled in previous releases).
- Improved the way we throw OperationCanceledExceptions in several code paths.
- Improved unit tests.

### v2.4

Released on November 20<sup>th</sup>, 2020
Expand Down
4 changes: 2 additions & 2 deletions source/Verifalia.Api/Credits/CreditsRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public async Task<Balance> GetBalanceAsync(CancellationToken cancellationToken =

var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down Expand Up @@ -187,7 +187,7 @@ private async Task<DailyUsageListSegment> ListDailyUsageSegmentedImplAsync(IRest
{
var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = def

var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private async Task<ValidationOverviewListSegment> ListSegmentedImplAsync(IRestCl
{
var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private async Task<QualityLevelListSegment> ListQualityLevelsSegmentedImplAsync(
{
var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public async Task<Validation> GetAsync(Guid id, WaitingStrategy waitingStrategy

var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down Expand Up @@ -194,7 +194,7 @@ public async Task<ValidationOverview> GetOverviewAsync(Guid id, WaitingStrategy

var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down Expand Up @@ -325,5 +325,56 @@ private async Task<ValidationEntryListSegment> ListEntriesSegmentedImplAsync(IRe
}
}
}

public async Task<Stream> ExportEntriesAsync(Guid validationId, ExportedEntriesFormat format, ValidationEntryListingOptions options = default, CancellationToken cancellationToken = default)
{
// Determines the acceptable MIME content type

var acceptableMimeContentType = format switch
{
ExportedEntriesFormat.Csv => WellKnownMimeContentTypes.TextCsv,
ExportedEntriesFormat.ExcelXls => WellKnownMimeContentTypes.ExcelXls,
ExportedEntriesFormat.ExcelXlsx => WellKnownMimeContentTypes.ExcelXlsx,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
};

// Sends the request to the Verifalia servers

var restClient = _restClientFactory.Build();

var response = await restClient
.InvokeAsync(HttpMethod.Get,
$"email-validations/{validationId:D}/entries",
headers: new Dictionary<string, object> { { "Accept", acceptableMimeContentType } },
cancellationToken: cancellationToken)
.ConfigureAwait(false);

// On success, returns the response body (which is formatted according to the requested export format)

if (response.StatusCode == HttpStatusCode.OK)
{
return await response
.Content
#if NET5_0_OR_GREATER
.ReadAsStreamAsync(cancellationToken)
#else
.ReadAsStreamAsync()
#endif
.ConfigureAwait(false);
}

// An unexpected HTTP status code has been received at this point

var responseBody = await response
.Content
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
#endif
.ConfigureAwait(false);

throw new VerifaliaException($"Unexpected HTTP response: {(int)response.StatusCode} {responseBody}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ public Task<Validation> SubmitAsync(IEnumerable<ValidationRequestEntry> entries,
cancellationToken: cancellationToken);
}

public async Task<Validation> SubmitAsync(ValidationRequest validationRequest, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
public async Task<Validation> SubmitAsync(ValidationRequest request, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
{
if (validationRequest == null) throw new ArgumentNullException(nameof(validationRequest));
if (request == null) throw new ArgumentNullException(nameof(request));

var restClient = _restClientFactory.Build();

Expand All @@ -93,22 +93,28 @@ public async Task<Validation> SubmitAsync(ValidationRequest validationRequest, W
var content = restClient
.Serialize(new
{
quality = validationRequest.Quality?.NameOrGuid,
deduplication = validationRequest.Deduplication?.NameOrGuid,
priority = validationRequest.Priority?.Value,
name = validationRequest.Name,
quality = request.Quality?.NameOrGuid,
deduplication = request.Deduplication?.NameOrGuid,
priority = request.Priority?.Value,
name = request.Name,
// Strips the milliseconds portion from the specified retention period, if any
retention = validationRequest.Retention == null
retention = request.Retention == null
? null
: new TimeSpan(validationRequest.Retention.Value.Days,
validationRequest.Retention.Value.Hours,
validationRequest.Retention.Value.Minutes,
validationRequest.Retention.Value.Seconds)
: new TimeSpan(request.Retention.Value.Days,
request.Retention.Value.Hours,
request.Retention.Value.Minutes,
request.Retention.Value.Seconds)
.ToString(),
callback = request.CompletionCallback == null
? null
: new
{
url = request.CompletionCallback.ToString()
},

// Non-file specific

entries = validationRequest.Entries,
entries = request.Entries,
});

// Send the request to the Verifalia servers
Expand Down Expand Up @@ -182,7 +188,13 @@ public async Task<Validation> SubmitAsync(FileValidationRequest request, Waiting
request.Retention.Value.Minutes,
request.Retention.Value.Seconds)
.ToString(),

callback = request.CompletionCallback == null
? null
: new
{
url = request.CompletionCallback.ToString()
},

// File-specific

startingRow = request.StartingRow,
Expand Down Expand Up @@ -259,7 +271,7 @@ private async Task<Validation> SubmitAsync(IRestClient restClient, Func<Cancella

var responseBody = await response
.Content
#if NET5_0
#if NET5_0_OR_GREATER
.ReadAsStringAsync(cancellationToken)
#else
.ReadAsStringAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ public interface IEmailValidationsRestClient
/// var validation = await SubmitAsync(new ValidationRequest(new[] { "[email protected]" }, waitingStrategy: new WaitingStrategy { waitForCompletion: true });
/// </code>
/// </example>
/// <param name="validationRequest">A <see cref="ValidationRequest"/> to submit for validation.</param>
/// <param name="request">A <see cref="ValidationRequest"/> to submit for validation.</param>
/// <param name="waitingStrategy">The strategy which rules out how to wait for the completion of the email validation.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Validation"/> object representing the submitted email validation job.</returns>
Task<Validation> SubmitAsync(ValidationRequest validationRequest, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default);
Task<Validation> SubmitAsync(ValidationRequest request, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default);

/// <summary>
/// Submits a new email validation for processing through a file, with support for the following formats:
Expand Down Expand Up @@ -397,5 +397,15 @@ public interface IEmailValidationsRestClient
/// <returns>An enumerable collection of <see cref="ValidationEntry"/> of the requested validation.</returns>
IAsyncEnumerable<ValidationEntry> ListEntriesAsync(Guid validationId, ValidationEntryListingOptions options = default, CancellationToken cancellationToken = default);
#endif

/// <summary>
/// Exports the validated entries for a given validation using the specified output format.
/// </summary>
/// <param name="validationId">The unique ID of the validation to list the entries for.</param>
/// <param name="format"></param>
/// <param name="options">The options for the listing operation.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A stream with the data exported in the requested format.</returns>
Task<Stream> ExportEntriesAsync(Guid validationId, ExportedEntriesFormat format, ValidationEntryListingOptions options = default, CancellationToken cancellationToken = default);
}
}
Loading

0 comments on commit 2d84ab6

Please sign in to comment.