VsRemote 0.0.2-alpha
dotnet add package VsRemote --version 0.0.2-alpha
NuGet\Install-Package VsRemote -Version 0.0.2-alpha
<PackageReference Include="VsRemote" Version="0.0.2-alpha" />
paket add VsRemote --version 0.0.2-alpha
#r "nuget: VsRemote, 0.0.2-alpha"
// Install VsRemote as a Cake Addin #addin nuget:?package=VsRemote&version=0.0.2-alpha&prerelease // Install VsRemote as a Cake Tool #tool nuget:?package=VsRemote&version=0.0.2-alpha&prerelease
Vs.Remote
A Visual Studio Code remote filesystem implementation with dotnet core backend, based on protocol buffers, gRPC and asp.net
Vs.Remote allows you to connect your Visual Studio Code workspace to a generic remote filesystem, where the backend is based on a custom dotnet core implementation.
You can store your files wherever you need them to be: on a database, in memory, on a real filesystem ...
The backend package is available on nuget.org as VsRemote, while the vscode extension must be installed manually; you can grab your .vsix extension file HERE
Create your own filesystem
Depending on how your backend works, you can choose to implement your filesystem as path-based or index-based.
If your backend provides some "keys" that can be used to obtain a direct reference to a file or folder, then you should proceed with a key-based approach. The key can be of any type, as long as it implements the IEquatable<T>
interface.
On the contrary, if your backend has no "keys" ready to be used, you need to rely on a path-based approach.
A key-based filesystem should be created extending the VsRemoteFileSystem<T>
abstract class, providing a type for the Key
; e.g.:
public class ExampleRelationalDatabaseBasedFilesystem : VsRemoteFileSystem<long>
A path-based filesystem, instead, should be created extending the VsRemoteFileSystem
abstract class.
Although these two abstract classes provide you most of the base validation, exception handling, and various other checks already made, if you need a more fine-grained and maybe a more efficient solution you can implement either the IVsRemoteFileSystem
or IVsRemoteFileSystem<T>
interfaces and write all the boring stuff by yourself.
If you need any more detail, please refer to the examples and classes documentation.
After having decided which of these approaches best fits your needs, you can proceed with the tricky part: implementing the filesystem APIs.
If you have chosen a path-based approach, the APIs will receive their parameters based on the full path to the files, split into string arrays, plus an IVsRemoteINode
, that is a file descriptor with this structure:
public interface IVsRemoteINode
{
string Name { get; } // the name of the file
VsRemoteFileType FileType { get; } // the file type (only File or Directory, atm)
long CTime { get; } // create time (unix timestamp)
long MTime { get; } // last modify time (unix timestamp)
long Size { get; } // size in bytes (0 for directories)
}
for example:
public abstract Task CreateDirectory(string directoryName, IVsRemoteINode parentDir, string[] parentPath);
If you have chosen a key-based approach instead, the APIs will receive their parameters based on the IVsRemoteINode<T>
class; this class adds to the IVsRemoteINode
the Key
and Parent
properties of type T, for example:
public abstract Task CreateDirectory(string directoryName, IVsRemoteINode<T> parentDir);
As you can see, the full path to the directory is missing, hence you shouldn't rely on that on your key-based implementation.
Whenever you need to create a IVsRemoteINode
instance you can instantiate an object of type VsRemoteINode
(or VsRemoteINode<T>
), that is the default implementation; for the special case of the root directory, there is a VsRemoteRootINode
facility.
Mounting the filesystems
The Vs.Remote subsystem requires the user to provide an instance of IVsRemoteFileSystemProvider
, that is, a factory that maps paths to IVsRemoteFilesystem
implementations.
This allows you, for example, to expose multiple filesystems on a single interface.
This interface has a single method FromPath
whose logic is: given a path and optionally an authentication token (more on this later), a tuple containing a particular filesystem and the path you just provided, relativized on the returned filesystem's path will be returned.
public (string RelativePath, IVsRemoteFileSystem RemoteFs) FromPath(string path, string? auth_token);
Bundled with Vs.Remote you'll find two different implementations of filesystem provider: RootFsProvider
and BasePathFsProvider
.
RootFsProvider
is the simplest case: you pass a single filesystem implementation to the constructor, and the provider will return that very same filesystem, whichever the path you request is.
BasePathFsProvider
maps different filesystems to different folders relative to the root filesystem, e.g.:
/first => ThisFilesystemImpl
/second => ThatFilesystemImpl
In this scenario, the returned path will be the one you passed to FromPath, minus the first path component, e.g. if
CreateDirectory("/second/hello/world")
is being called, the request will be forwarded as:
ThatFilesystemImpl.CreateDirectory("/hello/world")
It is possible, although discouraged, to provide your own implementation of IVsRemoteFileSystemProvider
. The general contract of this interface is: whichever the passed path is, you should always return a filesystem.
The only scenario in which you are required to supply your own provider implementation is that in which you need to differentiate the filesystem behavior based on the requesting user; more about this in the next chapter.
Authentication and permission handling
Vs.Remote supports both key-based and username/password authentication, but there is no predefined authentication backend provided: you have to implement your own.
Vs.Remote session handling is token based: the authentication method must return a session token that is verified before any filesystem action.
In order to create an authentication backend, you should extend the VsRemoteBaseAuthenticator
abstract class and override either Authenticate(string auth_key)
or Authenticate(string username, string password)
; these methods return a VsRemoteAuthenticateResult
object that can be instantiated using static constructors present on the class itself, that are:
VsRemoteAuthenticateResult.Authenticated(session_token)
VsRemoteAuthenticateResult.InvalidAuthKey
VsRemoteAuthenticateResult.InvalidUsernameOrPassword
VsRemoteAuthenticateResult.AuthenticationError(errorMessage) // errorMessage is optional
Then you have to implement the ValidateToken(string auth_token)
method that returns an enum VsRemoteAuthenticationStatus
- valid token ⇒ return
AUTHENTICATED
- invalid token (for whatever reason) ⇒ return
EXPIRED
- authentication backend unavailable or faulty ⇒ return
AUTHENTICATION_ERROR
Other enum values should not be used as return values for ValidateToken
.
Whenever a token expires (i.e., you return EXPIRED
from ValidateToken
) the vscode extension automatically calls the Login method again in order to obtain a new token without any kind of user feedback inside the editor.
The Sample project contains an example of a token-based authentication backend with expiring session tokens inside the SampleAuthKeyAuthentication.cs
file.
Now, if you need to differentiate the filesystem permissions based on the session, you have to dig a bit more. The entry point for implementing this feature is the IVsRemoteFileSystemProvider
interface.
As previously stated, the FromPath
method takes the auth_token
as second parameter; in the provided implementations, this parameter is ignored.
If, for example, you want to provide a read-only and a read-write version of the very same filesystem, based on the requesting user, you might check the auth_token
, verify if that token is owned by a user that has read-only or read-write permissions, and return the filesystem object accordingly.
Initialize the server
In order to initialize the server-side of Vs.Remote you just have to add the Vs.Remote service via the IServiceCollection.AddVsRemote( )
extension method, then map the service to the host via the IEndpointRouteBuilder.MapVsRemote( )
extension method.
The simplest Main method possible is:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
IVsRemoteFileSystemProvider fsProvider =
new RootFsProvider(
new InMemoryIndexedDictionaryFilesystem());
builder.Services.AddVsRemote(fsProvider);
var app = builder.Build();
app.MapVsRemote();
app.Run();
}
The AddVsRemote
method accepts a mandatory IVsRemoteFileSystem
provider, along with an optional IVsRemoteAuthenticator
to provide authentication. Lastly, you can provide an action based on Grpc.AspNetCore.Server.GrpcServiceOptions
that will be forwarded to the underlying AddGrpc( )
call.
The default for this object are:
MaxReceiveMessageSize = int.MaxValue;
MaxSendMessageSize = int.MaxValue;
If you need more info on these parameters and the other parameters available, please refer to the official documentation.
The mapping function MapVsRemote
receives an optional action based on the VsRemoteOptions
that provides only these two options:
public string RootFsPath { get; set; } = "/";
public bool EnableReflectionService { get; set; } = false;
The first one allows you to set the gRPC service on a path other than the default root path ( "/" ); this requires you to have a reverse proxy or other similar facility, because the currently adopted client library does not allow you to invoke gRPC services on a different path.
The EnableReflectionService
just enables the gRPC reflection service for debugging purposes; if you need any more detail, please refer to the IEndpointRouteBuilder.MapGrpcReflectionService( )
extension method official documentation.
Configure the Visual Studio Code extension
After having installed the vscode Vs.Remote extension, in order to configure the available connections you just have to add a "vs.remote" json block inside your settings.json
file following this sample schema:
"vs.remote": {
"remotes": {
"test_conn_1": {
"host": "localhost",
"port": 5229
},
"test_conn_2": {
"host": "localhost",
"port": 5229,
"auth_key": "1234567890"
},
"test_conn_3": {
"host": "localhost",
"port": 5229,
"username": "guest"
}
}
}
Here test_conn_1 is configured as anonymous / unauthenticated, test_conn_2 uses an authentication key, and test_conn_3 adopts the username/password auth scheme; you can configure the "password" field inside the configuration file, otherwise the password will be asked by vscode as soon as you try to connect to that endpoint. Passwords are never stored anywhere automatically.
Sizes and limits
If you want to enforce a file size limit on the filesystem, you shouldn't do it inside your filesystem implementation, instead, you should better work on the system configuration following these steps:
Add the
GrpcServiceOptions
flags:builder.Services.AddVsRemote(fsProvider, options => { options.MaxReceiveMessageSize = <int limit in bytes> options.MaxSendMessageSize = <int limit in bytes> });
Unlock the Kestrel file max request body size:
builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = <int limit in bytes> });
And that's it.
Sample Project
The sample project contains various elements that will surely help you implementing your own filesystem extension; there you'll find two filesystem implementation stubs:
InMemoryIndexedDictionaryFilesystem
is a very basic, key-based, in-memory filesystem, backed by aConcurrentDictionary
LocalFolderFilesystem
is a path-based filesystem mapper for a local directory
along with SampleAuthKeyAuthentication
: a simple authentication mechanism that implements auth_key authentication and expiring tokens.
A sample usage of these classes can be found inside the Program.cs
source file.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
-
net6.0
- Google.Protobuf (>= 3.21.12)
- Grpc.AspNetCore.Server (>= 2.51.0)
- Grpc.AspNetCore.Server.Reflection (>= 2.51.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.0.2-alpha | 128 | 1/4/2023 |