WopiHost.Abstractions
5.0.5
dotnet add package WopiHost.Abstractions --version 5.0.5
NuGet\Install-Package WopiHost.Abstractions -Version 5.0.5
<PackageReference Include="WopiHost.Abstractions" Version="5.0.5" />
<PackageVersion Include="WopiHost.Abstractions" Version="5.0.5" />
<PackageReference Include="WopiHost.Abstractions" />
paket add WopiHost.Abstractions --version 5.0.5
#r "nuget: WopiHost.Abstractions, 5.0.5"
#:package WopiHost.Abstractions@5.0.5
#addin nuget:?package=WopiHost.Abstractions&version=5.0.5
#tool nuget:?package=WopiHost.Abstractions&version=5.0.5
WopiHost.Abstractions
A .NET library containing the core abstractions and interfaces for building WOPI (Web Application Open Platform Interface) host implementations. This package defines the contracts that WOPI hosts must implement to integrate with Office Online Server.
Features
- Core Interfaces: Essential interfaces for WOPI host implementation
- Storage Abstractions: Abstract file and folder operations
- Security Contracts: Authentication and authorization interfaces
- WOPI Models: Standard WOPI data models and enums
- Lock Management: File locking and concurrency control interfaces
- Host Capabilities: WOPI host capability definitions
Installation
dotnet add package WopiHost.Abstractions
Quick Start
Basic Interface Usage
using WopiHost.Abstractions;
// Implement a custom storage provider
public class CustomStorageProvider : IWopiStorageProvider
{
public Task<T?> GetWopiResource<T>(string identifier, CancellationToken cancellationToken = default)
where T : class, IWopiResource
{
// Your implementation here
throw new NotImplementedException();
}
public IAsyncEnumerable<IWopiFile> GetWopiFiles(string? identifier = null, string? searchPattern = null, CancellationToken cancellationToken = default)
{
// Your implementation here
throw new NotImplementedException();
}
public IAsyncEnumerable<IWopiFolder> GetWopiContainers(string? identifier = null, CancellationToken cancellationToken = default)
{
// Your implementation here
throw new NotImplementedException();
}
public IWopiFolder RootContainerPointer { get; } = new CustomFolder("root", "root-id");
}
Hero Scenarios
1. Custom Cloud Storage Integration
Integrate with any cloud storage provider (Azure Blob, AWS S3, Google Cloud, etc.):
public class AzureBlobStorageProvider : IWopiStorageProvider, IWopiWritableStorageProvider
{
private readonly BlobServiceClient _blobServiceClient;
private readonly string _containerName;
public AzureBlobStorageProvider(BlobServiceClient blobServiceClient, string containerName)
{
_blobServiceClient = blobServiceClient;
_containerName = containerName;
}
public async Task<T?> GetWopiResource<T>(string identifier, CancellationToken cancellationToken = default)
where T : class, IWopiResource
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
var blobClient = containerClient.GetBlobClient(identifier);
if (typeof(T) == typeof(IWopiFile))
{
var exists = await blobClient.ExistsAsync(cancellationToken);
if (exists.Value)
{
return new AzureBlobFile(blobClient, identifier) as T;
}
}
else if (typeof(T) == typeof(IWopiFolder))
{
// Handle folder logic
return new AzureBlobFolder(identifier) as T;
}
return null;
}
public async IAsyncEnumerable<IWopiFile> GetWopiFiles(string? identifier = null, string? searchPattern = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
var prefix = identifier ?? "";
await foreach (var blobItem in containerClient.GetBlobsAsync(prefix: prefix, cancellationToken: cancellationToken))
{
if (blobItem.Properties.ContentType?.StartsWith("application/") == true)
{
var blobClient = containerClient.GetBlobClient(blobItem.Name);
yield return new AzureBlobFile(blobClient, blobItem.Name);
}
}
}
// Implement other required methods...
}
public class AzureBlobFile : IWopiFile
{
private readonly BlobClient _blobClient;
public AzureBlobFile(BlobClient blobClient, string identifier)
{
_blobClient = blobClient;
Identifier = identifier;
}
public string Identifier { get; }
public string Name => Path.GetFileNameWithoutExtension(_blobClient.Name);
public string Extension => Path.GetExtension(_blobClient.Name).TrimStart('.');
public bool Exists => true; // We know it exists if we got here
public long Length => 0; // Would need to fetch properties
public long Size => Length;
public DateTime LastWriteTimeUtc => DateTime.UtcNow; // Would need to fetch properties
public string? Version => null;
public byte[]? Checksum => null;
public string Owner => "system"; // Would need to fetch metadata
public async Task<Stream> GetReadStream(CancellationToken cancellationToken = default)
{
var response = await _blobClient.DownloadStreamingAsync(cancellationToken: cancellationToken);
return response.Value.Content;
}
public async Task<Stream> GetWriteStream(CancellationToken cancellationToken = default)
{
// Implementation for write stream
throw new NotImplementedException();
}
}
2. Database-Backed Document Storage
Store documents in a database with metadata:
public class DatabaseStorageProvider : IWopiStorageProvider, IWopiWritableStorageProvider
{
private readonly ApplicationDbContext _context;
private readonly IBlobStorage _blobStorage;
public async Task<T?> GetWopiResource<T>(string identifier, CancellationToken cancellationToken = default)
where T : class, IWopiResource
{
var document = await _context.Documents
.FirstOrDefaultAsync(d => d.Id == identifier, cancellationToken);
if (document == null) return null;
if (typeof(T) == typeof(IWopiFile))
{
return new DatabaseFile(document, _blobStorage) as T;
}
else if (typeof(T) == typeof(IWopiFolder))
{
return new DatabaseFolder(document) as T;
}
return null;
}
public async IAsyncEnumerable<IWopiFile> GetWopiFiles(string? identifier = null, string? searchPattern = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var query = _context.Documents.AsQueryable();
if (!string.IsNullOrEmpty(identifier))
{
query = query.Where(d => d.ParentId == identifier);
}
if (!string.IsNullOrEmpty(searchPattern))
{
query = query.Where(d => EF.Functions.Like(d.Name, searchPattern));
}
await foreach (var document in query.AsAsyncEnumerable())
{
yield return new DatabaseFile(document, _blobStorage);
}
}
// Implement other methods...
}
public class DatabaseFile : IWopiFile
{
private readonly Document _document;
private readonly IBlobStorage _blobStorage;
public DatabaseFile(Document document, IBlobStorage blobStorage)
{
_document = document;
_blobStorage = blobStorage;
}
public string Identifier => _document.Id;
public string Name => _document.Name;
public string Extension => _document.Extension;
public bool Exists => true;
public long Length => _document.Size;
public long Size => _document.Size;
public DateTime LastWriteTimeUtc => _document.LastModified;
public string? Version => _document.Version;
public byte[]? Checksum => _document.Checksum;
public string Owner => _document.OwnerId;
public async Task<Stream> GetReadStream(CancellationToken cancellationToken = default)
{
return await _blobStorage.GetStreamAsync(_document.BlobPath, cancellationToken);
}
public async Task<Stream> GetWriteStream(CancellationToken cancellationToken = default)
{
return await _blobStorage.GetWriteStreamAsync(_document.BlobPath, cancellationToken);
}
}
3. Custom Security Implementation
Implement custom authentication and authorization:
public class CustomSecurityHandler : IWopiSecurityHandler
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
public async Task<WopiUserPermissions> GetUserPermissionsAsync(string userId, string resourceId)
{
var user = await _userService.GetUserAsync(userId);
var resource = await GetResourceAsync(resourceId);
var permissions = WopiUserPermissions.None;
if (user.CanRead(resource))
{
permissions |= WopiUserPermissions.Read;
}
if (user.CanWrite(resource))
{
permissions |= WopiUserPermissions.Write;
}
if (user.CanDelete(resource))
{
permissions |= WopiUserPermissions.Delete;
}
return permissions;
}
public async Task<bool> ValidateTokenAsync(string token, string resourceId)
{
try
{
var claims = _tokenService.ValidateToken(token);
var userId = claims.FindFirst("user_id")?.Value;
if (string.IsNullOrEmpty(userId))
{
return false;
}
var permissions = await GetUserPermissionsAsync(userId, resourceId);
return permissions != WopiUserPermissions.None;
}
catch
{
return false;
}
}
// Implement other required methods...
}
public class CustomLockProvider : IWopiLockProvider
{
private readonly IDistributedCache _cache;
private readonly ILogger<CustomLockProvider> _logger;
public async Task<bool> LockAsync(string resourceId, string lockId, TimeSpan timeout)
{
try
{
var lockKey = $"wopi:lock:{resourceId}";
var existingLock = await _cache.GetStringAsync(lockKey);
if (!string.IsNullOrEmpty(existingLock) && existingLock != lockId)
{
return false; // Resource is already locked by someone else
}
await _cache.SetStringAsync(lockKey, lockId, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = timeout
});
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to acquire lock for resource {ResourceId}", resourceId);
return false;
}
}
public async Task<bool> UnlockAsync(string resourceId, string lockId)
{
try
{
var lockKey = $"wopi:lock:{resourceId}";
var existingLock = await _cache.GetStringAsync(lockKey);
if (existingLock != lockId)
{
return false; // Lock doesn't match
}
await _cache.RemoveAsync(lockKey);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to release lock for resource {ResourceId}", resourceId);
return false;
}
}
// Implement other required methods...
}
Core Interfaces
IWopiStorageProvider
Base interface for read-only storage operations.
public interface IWopiStorageProvider
{
Task<T?> GetWopiResource<T>(string identifier, CancellationToken cancellationToken = default)
where T : class, IWopiResource;
IAsyncEnumerable<IWopiFile> GetWopiFiles(string? identifier = null, string? searchPattern = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<IWopiFolder> GetWopiContainers(string? identifier = null, CancellationToken cancellationToken = default);
IWopiFolder RootContainerPointer { get; }
Task<IEnumerable<IWopiResource>> GetAncestorsAsync(string identifier, CancellationToken cancellationToken = default);
}
IWopiWritableStorageProvider
Extends IWopiStorageProvider with write operations.
public interface IWopiWritableStorageProvider : IWopiStorageProvider
{
Task<IWopiFile> CreateFileAsync(string identifier, Stream content, CancellationToken cancellationToken = default);
Task<IWopiFolder> CreateFolderAsync(string identifier, CancellationToken cancellationToken = default);
Task DeleteFileAsync(string identifier, CancellationToken cancellationToken = default);
Task DeleteFolderAsync(string identifier, CancellationToken cancellationToken = default);
Task<IWopiFile> RenameFileAsync(string identifier, string newName, CancellationToken cancellationToken = default);
Task<IWopiFolder> RenameFolderAsync(string identifier, string newName, CancellationToken cancellationToken = default);
}
IWopiFile
Represents a file in the WOPI system.
public interface IWopiFile : IWopiResource
{
string Owner { get; }
bool Exists { get; }
long Length { get; }
DateTime LastWriteTimeUtc { get; }
string Extension { get; }
string? Version { get; }
byte[]? Checksum { get; }
long Size { get; }
Task<Stream> GetReadStream(CancellationToken cancellationToken = default);
Task<Stream> GetWriteStream(CancellationToken cancellationToken = default);
}
IWopiFolder
Represents a folder/container in the WOPI system.
public interface IWopiFolder : IWopiResource
{
// Inherits Name and Identifier from IWopiResource
}
IWopiSecurityHandler
Handles authentication and authorization.
public interface IWopiSecurityHandler
{
Task<WopiUserPermissions> GetUserPermissionsAsync(string userId, string resourceId);
Task<bool> ValidateTokenAsync(string token, string resourceId);
Task<string> GetUserIdAsync(string token);
}
IWopiLockProvider
Manages file locking for concurrent access.
public interface IWopiLockProvider
{
Task<bool> LockAsync(string resourceId, string lockId, TimeSpan timeout);
Task<bool> UnlockAsync(string resourceId, string lockId);
Task<string?> GetLockAsync(string resourceId);
Task<bool> RefreshLockAsync(string resourceId, string lockId, TimeSpan timeout);
}
WOPI Models
WopiCheckFileInfo
Standard WOPI file information model.
public class WopiCheckFileInfo
{
public string BaseFileName { get; set; }
public string OwnerId { get; set; }
public long Size { get; set; }
public string UserId { get; set; }
public string Version { get; set; }
public string Sha256 { get; set; }
public bool UserCanWrite { get; set; }
public bool UserCanNotWriteRelative { get; set; }
public bool UserCanRename { get; set; }
public bool UserCanAttend { get; set; }
public bool UserCanPresent { get; set; }
public bool UserCanEdit { get; set; }
public bool UserCanView { get; set; }
public bool UserCanDelete { get; set; }
// ... more properties
}
WopiHostCapabilities
Defines what the WOPI host supports.
public class WopiHostCapabilities : IWopiHostCapabilities
{
public bool SupportsCoauth { get; set; }
public bool SupportsCobalt { get; set; }
public bool SupportsFolders { get; set; } = true;
public bool SupportsContainers { get; set; } = true;
public bool SupportsLocks { get; set; }
public bool SupportsGetLock { get; set; }
public bool SupportsExtendedLockLength { get; set; } = true;
public bool SupportsEcosystem { get; set; } = true;
public bool SupportsGetFileWopiSrc { get; set; }
public IEnumerable<string> SupportedShareUrlTypes { get; set; } = [];
public bool SupportsScenarioLinks { get; set; }
public bool SupportsSecureStore { get; set; }
public bool SupportsFileCreation { get; set; }
public bool SupportsUpdate { get; set; } = true;
public bool SupportsRename { get; set; } = true;
public bool SupportsDeleteFile { get; set; } = true;
public bool SupportsUserInfo { get; set; } = true;
}
Enums
PermissionEnum
File permission levels.
public enum PermissionEnum
{
None = 0,
Read = 1,
Write = 2,
Delete = 4,
All = Read | Write | Delete
}
WopiFileOperations
Standard WOPI file operations.
public enum WopiFileOperations
{
GetFile,
PutFile,
Lock,
Unlock,
RefreshLock,
GetLock,
CheckFileInfo,
PutRelativeFile,
DeleteFile,
RenameFile,
PutUserInfo,
ReadSecureStore,
GetRestrictedLink,
RevokeRestrictedLink,
CheckContainerInfo,
GetContainer,
CreateContainer,
DeleteContainer,
CheckEcosystem,
GetEcosystem,
GetFileWopiSrc,
EnumerateChildren,
CheckFolderInfo,
PutFileWopiSrc,
DeleteFileWopiSrc,
PutRelativeFileLocal,
GetFileWopiSrc,
PutFileWopiSrc,
DeleteFileWopiSrc,
PutRelativeFileLocal,
GetFileWopiSrc,
PutFileWopiSrc,
DeleteFileWopiSrc,
PutRelativeFileLocal
}
Dependencies
Microsoft.AspNetCore.Authorization: For authorization supportMicrosoft.IdentityModel.Tokens: For JWT token handling
Examples
Custom Resource Implementation
public class CustomFile : IWopiFile
{
public CustomFile(string path, string identifier)
{
Path = path;
Identifier = identifier;
var fileInfo = new FileInfo(path);
Name = fileInfo.Name;
Exists = fileInfo.Exists;
Length = fileInfo.Length;
LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
Extension = fileInfo.Extension.TrimStart('.');
Owner = Environment.UserName;
}
public string Path { get; }
public string Identifier { get; }
public string Name { get; }
public bool Exists { get; }
public long Length { get; }
public long Size => Length;
public DateTime LastWriteTimeUtc { get; }
public string Extension { get; }
public string? Version => null;
public byte[]? Checksum => null;
public string Owner { get; }
public Task<Stream> GetReadStream(CancellationToken cancellationToken = default)
{
return Task.FromResult<Stream>(File.OpenRead(Path));
}
public Task<Stream> GetWriteStream(CancellationToken cancellationToken = default)
{
return Task.FromResult<Stream>(File.OpenWrite(Path));
}
}
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please read our Contributing Guidelines for details on our code of conduct and the process for submitting pull requests.
Support
For support and questions:
- Create an issue on GitHub
- Check the documentation
- Review the WOPI specification
| Product | Versions 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 is compatible. 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. |
-
net10.0
- Microsoft.AspNetCore.Authorization (>= 10.0.3)
- Microsoft.IdentityModel.Tokens (>= 8.16.0)
-
net8.0
- Microsoft.AspNetCore.Authorization (>= 10.0.3)
- Microsoft.IdentityModel.Tokens (>= 8.16.0)
-
net9.0
- Microsoft.AspNetCore.Authorization (>= 10.0.3)
- Microsoft.IdentityModel.Tokens (>= 8.16.0)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on WopiHost.Abstractions:
| Package | Downloads |
|---|---|
|
WopiHost.Discovery
WopiHost.Discovery Class Library |
|
|
WopiHost.Core
WopiHost.Core Class Library |
|
|
WopiHost.FileSystemProvider
WopiHost.FileSystemProvider Class Library |
|
|
WopiHost.MemoryLockProvider
WopiHost.MemoryLockProvider Class Library |
GitHub repositories
This package is not used by any popular GitHub repositories.